1 /*
2  * Copyright (C) 2021 Tetsuya Isaki
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
18  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
20  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include "ImageLoaderPNG.h"
27 #include "StringUtil.h"
28 #include <cstring>
29 #include <errno.h>
30 #include <png.h>
31 
32 static void png_read(png_structp png, png_bytep data, png_size_t length);
33 
34 // コンストラクタ
ImageLoaderPNG(InputStream * stream_,const Diag & diag_)35 ImageLoaderPNG::ImageLoaderPNG(InputStream *stream_, const Diag& diag_)
36 	: inherited(stream_, diag_)
37 {
38 }
39 
40 // デストラクタ
~ImageLoaderPNG()41 ImageLoaderPNG::~ImageLoaderPNG()
42 {
43 }
44 
45 // stream が PNG なら true を返す。
46 bool
Check() const47 ImageLoaderPNG::Check() const
48 {
49 	uint8 magic[4];
50 
51 	auto n = stream->Peek(&magic, sizeof(magic));
52 	if (n < sizeof(magic)) {
53 		Trace(diag, "%s: Read(magic) failed: %s", __method__, strerror(errno));
54 		return false;
55 	}
56 	// マジックを確認
57 	if (png_sig_cmp(magic, 0, sizeof(magic)) != 0) {
58 		Trace(diag, "%s: Bad magic", __method__);
59 		return false;
60 	}
61 	Trace(diag, "%s: OK", __method__);
62 	return true;
63 }
64 
65 // stream から画像をロードする。
66 bool
Load(Image & img)67 ImageLoaderPNG::Load(Image& img)
68 {
69 	png_structp png;
70 	png_infop info;
71 	png_uint_32 width;
72 	png_uint_32 height;
73 	int bitdepth;
74 	int color_type;
75 	int interlace_type;
76 	int compression_type;
77 	int filter_type;
78 	std::vector<png_bytep> lines;
79 	bool rv;
80 
81 	rv = false;
82 	png = NULL;
83 	info = NULL;
84 
85 	png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
86 		NULL, NULL, NULL);
87 	if (__predict_false(png == NULL)) {
88 		return false;
89 	}
90 	info = png_create_info_struct(png);
91 	if (__predict_false(info == NULL)) {
92 		goto done;
93 	}
94 
95 	// libpng 内のエラーからは大域ジャンプで戻ってくるらしい…
96 	if (setjmp(png_jmpbuf(png))) {
97 		goto done;
98 	}
99 
100 	// コールバック設定
101 	png_set_read_fn(png, stream, png_read);
102 
103 	// ヘッダを読み込む
104 	png_read_info(png, info);
105 	png_get_IHDR(png, info, &width, &height, &bitdepth,
106 		&color_type, &interlace_type, &compression_type, &filter_type);
107 	Debug(diag, "IHDR width=%d height=%d bitdepth=%d", width, height, bitdepth);
108 	Debug(diag, "IHDR colortype=%s interlace=%d compression=%d filter=%d",
109 		ColorType2str(color_type).c_str(), interlace_type,
110 		compression_type, filter_type);
111 
112 	// color_type によっていろいろ設定が必要。
113 	// see libpng(4)
114 	if (color_type == PNG_COLOR_TYPE_PALETTE) {
115 		png_set_palette_to_rgb(png);
116 	}
117 	if ((color_type & PNG_COLOR_MASK_COLOR) == 0) {
118 		if (bitdepth < 8) {
119 			png_set_expand_gray_1_2_4_to_8(png);
120 		}
121 		png_set_gray_to_rgb(png);
122 	}
123 	// Alpha 無視
124 	png_set_strip_alpha(png);
125 
126 	img.Create(width, height);
127 
128 	// スキャンラインメモリのポインタ配列
129 	lines.resize(img.GetHeight());
130 	for (int y = 0, end = lines.size(); y < end; y++) {
131 		lines[y] = img.buf.data() + (y * img.GetStride());
132 	}
133 
134 	png_read_image(png, lines.data());
135 	png_read_end(png, info);
136 	rv = true;
137 
138  done:
139 	png_destroy_read_struct(&png, &info, (png_infopp)NULL);
140 	return rv;
141 }
142 
143 // PNG の color type
144 /*static*/ std::string
ColorType2str(int type)145 ImageLoaderPNG::ColorType2str(int type)
146 {
147 	switch (type) {
148 	 case PNG_COLOR_TYPE_GRAY:
149 		return "Gray";
150 	 case PNG_COLOR_TYPE_PALETTE:
151 		return "Palette";
152 	 case PNG_COLOR_TYPE_RGB:
153 		return "RGB";
154 	 case PNG_COLOR_TYPE_RGBA:
155 		return "RGBA";
156 	 case PNG_COLOR_TYPE_GRAY_ALPHA:
157 		return "GrayA";
158 	 default:
159 		return string_format("%d(?)", type);
160 	}
161 }
162 
163 // コールバック
164 static void
png_read(png_structp png,png_bytep data,png_size_t length)165 png_read(png_structp png, png_bytep data, png_size_t length)
166 {
167 	InputStream *stream = (InputStream *)png_get_io_ptr(png);
168 
169 	size_t total = 0;
170 	while (total < length) {
171 		auto r = stream->Read((char *)data + total, length - total);
172 		if (r <= 0)
173 			break;
174 		total += r;
175 	}
176 }
177