mameブログ

mameという男がやりたい事をやっている記録

JPEG画像の入出力

■ソフトのお勉強

外出自粛期間であるため、お勉強をしています。
私、情報系でありながらソフトウェア開発が不得意です。
このコンプレックス解消のため良い機会として勉強し直します。

今回選んだテーマは、

JPEG画像の入出力」

です。

JPEGの扱いに関しては、
JPEG画像を取り扱うためのライブラリの取得
JPEG画像を取得して画素値を抽出する方法
JPEG画像を新たに保存する方法
が学べれば応用が利くと思います。
例えば、画像に何らかのフィルタをかけたい、というのであれば。
①⇒②⇒「画素値にフィルタをかける」⇒③とやれば良いのです。
「画素値にフィルタをかける」をモジュール化すると汎用性も高くなります。
この記事では、①②③について扱います。
開発環境はVS2017で、OSはWindows10です。
開発言語はC++を想定しています。

JPEGライブラリの取得

JPEGライブラリは様々ありますが、今回は「libjpeg」を使います。
(その名の通りですね)

libjpegはオープンソースとなります。
オープンソースですので、これを使ったアプリケーションなど再配布をする場合は取り扱いにご注意下さい。)

今回はVS2017を使用して、libjpegをビルドします。
以下のHPを参考にすれば万事うまくいきました。
hnakai0909.hatenablog.com

ここでは、jpegsr9b.zipを展開されていました。
私が実施した時は9dバージョンとなっておりました。
参考サイトと同様に、renを使いVS用のファイルを生成していきます。
jpeg.slnとapps.slnの2つのプロジェクトファイルが作られます。
(生成する、作ると言っていますが、実態は名前変更しただけです)

jpeg.slnをビルドすると、jpeg.libが生成されます。
②③はこのjpeg.libと、各種ヘッダファイルを使います。
※プラットフォームツールセットやWindowsSDKバージョンがお使いの環境からズレている場合があります、自身の環境に合わせてビルドしましょう。

JPEGファイルの入出力

手っ取り早くサンプルコードを載せます。
JPEG画像の読み込みと、JPEG画像の生成を記述しています。
JPEG画像生成時は、JPEG画像の読み込み時に取得した画素値を変調しています。
(変調が非常にテキトーですみません…)

#include <stdio.h>
#include <cstdlib>
#include <cstring>
#include "jpeglib.h"

#define	RET_TRUE	0
#define	RET_FALSE	-1

//JPG画像を読み込み&書き込み処理する関数を実行する
int main(int argc, char* argv[]) {

	//エラー用変数
	errno_t error;

	//入力変数を格納
	char *inFileName = argv[1];		//入力ファイル名
	char *outFileName = argv[2];	//出力ファイル名

	//------JPEG読み込み------//

	//  (1)画像データを扱う構造体を確保
	struct jpeg_decompress_struct inInfo;	//画像データ構造体を定義
	jpeg_create_decompress(&inInfo);		//構造体初期化処理
	struct jpeg_error_mgr jErr;				//エラー情報構造体を定義
	inInfo.err = jpeg_std_error(&jErr);		//デフォルト値で設定

	//  (2)JPEGファイルをオープン
	FILE *inFile;
	error = fopen_s(&inFile, inFileName, "rb");		//ファイル読み込み
	if (error != 0) {
		return RET_FALSE;
	}
	jpeg_stdio_src(&inInfo, inFile);		//ファイルポインタとinInfoを渡す

	//  (3)ヘッダ読み込み
	if (jpeg_read_header(&inInfo, TRUE) != JPEG_HEADER_OK) {
		return RET_FALSE;
	}
	int width = inInfo.image_width;		//幅
	int height = inInfo.image_height;	//高さ
	int ch = inInfo.num_components;		//チャネル数

	//  (4)展開処理を開始
	jpeg_start_decompress(&inInfo);
	if (inInfo.out_color_space != JCS_RGB) {	//sRGB以外を受け付けない
		return RET_FALSE;
	}

	//  (5)画素値を取得
	unsigned char *inFileData = (unsigned char*)calloc(sizeof(unsigned char) * width * height * ch, 1);	//3ch分の画素値配列を確保する
	JSAMPROW tmpR = NULL;	//1行分のメモリ
	if ((tmpR = (JSAMPROW)calloc(sizeof(JSAMPLE) * width * ch, 1) )== NULL) {
		return RET_FALSE;
	}
	JSAMPROW assign;
	for (int h = 0; h < height; h++) {
		jpeg_read_scanlines(&inInfo, &tmpR, 1);		//1行分の画素値を代入
		assign = tmpR;
		for (int w = 0; w < width; w++) {
			for (int c = 0; c < ch; c++) {
				inFileData[(h * width + w)* ch + c] = *assign++;	//確保した配列に1行ずつ代入していく
			}
		}
	}

	//  (6)構造体の破棄、メモリ解放
	jpeg_finish_decompress(&inInfo);	//展開処理を終了
	jpeg_destroy_decompress(&inInfo);	//JPEG構造体の破棄
	free(tmpR);
	fclose(inFile);


	//------JPEG書き込み------//

	//  (1)画像データを扱う構造体を確保	
	struct jpeg_compress_struct outInfo;	//画像データ構造体を定義
	jpeg_create_compress(&outInfo);			//構造体初期化処理
	outInfo.err = jpeg_std_error(&jErr);	//デフォルト値で設定

	//  (2)JPEGファイルをオープン
	FILE *outFile;
	error = fopen_s(&outFile, outFileName, "wb");	//ファイル生成
	if (error != 0) {
		return RET_FALSE;
	}
	jpeg_stdio_dest(&outInfo, outFile);		//ファイルポインタとoutInfoを渡す

	//  (3)ヘッダ書き込み
	outInfo.image_width = width;		//幅
	outInfo.image_height = height;		//高さ
	outInfo.input_components = ch;		//チャネル数
	outInfo.in_color_space = JCS_RGB;	//色空間
	jpeg_set_defaults(&outInfo);		//その他のパラメータ設定

	//  (4)展開処理を開始
	jpeg_start_compress(&outInfo, TRUE);

	//  (5)画素値を書き込み
	JSAMPARRAY tmpO = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * height);	//1画像分のメモリ
	for (int h = 0; h < height; h++) {
		if ((tmpO[h] = (JSAMPROW)calloc(sizeof(JSAMPLE), width * ch)) == NULL) {
			return RET_FALSE;
		}
	}

	for (int h = 0; h < height; h++) {
		for (int w = 0; w < width; w++) {
			for (int c = 0; c < ch; c++) {
				//画素値を変調(XXX足して255を超えるようなら255にクリップする
				unsigned char s = 100;
				unsigned char t = (inFileData[(h * width + w)* ch + c]);
				if ((int)(s)+(int)(t) > 255) {
					t = 255;
				}
				else {
					t = (unsigned char)((int)(s)+(int)(t));
				}
				tmpO[h][w * ch + c] = t;
			}
		}
	}
	jpeg_write_scanlines(&outInfo, tmpO, height);	//書き込み

	//  (6)構造体の破棄、メモリ解放
	jpeg_finish_compress(&outInfo);		//展開処理を終了
	jpeg_destroy_compress(&outInfo);	//JPEG構造体の破棄
	fclose(outFile);
	free(tmpO);

	free(inFileData);

	return RET_TRUE;
}

画素値の変更は非常にシンプルです。
sという変数に与えた数だけ画素値にプラスします。
255を上回るようであれば、255に丸めます。

これを実行すると、JPEG画像は入力/出力で以下のように変わります。

f:id:mizzzo:20200409120618j:plain
before
f:id:mizzzo:20200409120640j:plain
after
当然ですが、画像が白っちゃけます。

フィルタをかけたい場合は、画素ごとに異なる値を足すなり掛ければ良いです。
画素値が取れたのでいかようにも編集は出来ます。

■おわりに

JPEGの取り扱いの基本について記載しました。
JPEGは画像形式として最もメジャーですので、参考となるHPが多数あります。
ぜひぜひ、色々と画像を編集して遊んでみましょう。

独習C++ 第4版

独習C++ 第4版