Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPBitmapFormat.h"
25 : #include "SPLog.h"
26 : #include "SPFilesystem.h"
27 : #include "webp/decode.h"
28 : #include "webp/encode.h"
29 :
30 : namespace STAPPLER_VERSIONIZED stappler::bitmap::webp {
31 :
32 4250 : static bool isWebpLossless(const uint8_t * data, size_t dataLen) {
33 4250 : if (dataLen <= 12) {
34 0 : return false;
35 : }
36 :
37 : static const char* WEBP_RIFF = "RIFF";
38 : static const char* WEBP_WEBP = "WEBPVP8L";
39 :
40 4250 : return memcmp(data, WEBP_RIFF, 4) == 0
41 4250 : && memcmp(static_cast<const unsigned char*>(data) + 8, WEBP_WEBP, 8) == 0;
42 : }
43 :
44 950 : static bool getWebpLosslessImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
45 950 : if (isWebpLossless(data.data(), data.size())) {
46 75 : auto reader = BytesViewTemplate<Endian::Big>(data.data() + 21, 4);
47 :
48 75 : auto b0 = reader.readUnsigned();
49 75 : auto b1 = reader.readUnsigned();
50 75 : auto b2 = reader.readUnsigned();
51 75 : auto b3 = reader.readUnsigned();
52 :
53 : // first 14 bits - width, last 14 bits - height
54 :
55 75 : width = (b0 | ((b1 & 0x3F) << 8)) + 1;
56 75 : height = (((b3 & 0xF) << 10) | (b2 << 2) | ((b1 & 0xC0) >> 6)) + 1;
57 :
58 75 : return true;
59 : }
60 875 : return false;
61 : }
62 :
63 3900 : static bool isWebp(const uint8_t * data, size_t dataLen) {
64 3900 : if (dataLen <= 12) {
65 0 : return false;
66 : }
67 :
68 : static const char* WEBP_RIFF = "RIFF";
69 : static const char* WEBP_WEBP = "WEBP";
70 :
71 3900 : return memcmp(data, WEBP_RIFF, 4) == 0
72 3900 : && memcmp(static_cast<const unsigned char*>(data) + 8, WEBP_WEBP, 4) == 0;
73 : }
74 :
75 875 : static bool getWebpImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
76 875 : if (isWebp(data.data(), data.size())) {
77 75 : auto reader = BytesViewTemplate<Endian::Little>(data.data() + 24, 6);
78 :
79 75 : auto b0 = reader.readUnsigned();
80 75 : auto b1 = reader.readUnsigned();
81 75 : auto b2 = reader.readUnsigned();
82 75 : auto b3 = reader.readUnsigned();
83 75 : auto b4 = reader.readUnsigned();
84 75 : auto b5 = reader.readUnsigned();
85 :
86 : // first 14 bits - width, last 14 bits - height
87 :
88 75 : width = (b0 | (b1 << 8) | (b2 << 8)) + 1;
89 75 : height = (b3 | (b4 << 8) | (b5 << 8)) + 1;
90 :
91 75 : return true;
92 : }
93 800 : return false;
94 : }
95 :
96 150 : static bool infoWebp(WebPDecoderConfig *config, const uint8_t *inputData, size_t size, ImageInfo &outputData) {
97 150 : if (WebPInitDecoderConfig(config) == 0) return false;
98 150 : if (WebPGetFeatures(inputData, size, &config->input) != VP8_STATUS_OK) return false;
99 150 : if (config->input.width == 0 || config->input.height == 0) return false;
100 :
101 150 : outputData.color = config->input.has_alpha ? PixelFormat::RGBA8888 : PixelFormat::RGB888;
102 150 : outputData.width = config->input.width;
103 150 : outputData.height = config->input.height;
104 :
105 150 : outputData.alpha = (config->input.has_alpha != 0) ? AlphaFormat::Unpremultiplied : AlphaFormat::Opaque;
106 150 : outputData.stride = (uint32_t)outputData.width * getBytesPerPixel(outputData.color);
107 150 : return true;
108 : }
109 :
110 50 : static bool infoWebp(const uint8_t *inputData, size_t size, ImageInfo &outputData) {
111 : WebPDecoderConfig config;
112 100 : return infoWebp(&config, inputData, size, outputData);
113 : }
114 :
115 100 : static bool loadWebp(const uint8_t *inputData, size_t size, BitmapWriter &outputData) {
116 : WebPDecoderConfig config;
117 100 : if (!infoWebp(&config, inputData, size, outputData)) {
118 0 : return false;
119 : }
120 :
121 100 : if (outputData.getStride) {
122 50 : outputData.stride = max((uint32_t)outputData.getStride(outputData.target, outputData.color, outputData.width),
123 100 : (uint32_t)outputData.width * getBytesPerPixel(outputData.color));
124 : }
125 :
126 100 : outputData.resize(outputData.target, outputData.stride * outputData.height);
127 :
128 100 : config.output.colorspace = config.input.has_alpha ? MODE_RGBA : MODE_RGB;
129 100 : config.output.u.RGBA.rgba = outputData.getData(outputData.target, 0);
130 100 : config.output.u.RGBA.stride = outputData.stride;
131 100 : config.output.u.RGBA.size = outputData.stride * outputData.height;
132 100 : config.output.is_external_memory = 1;
133 :
134 100 : if (WebPDecode(inputData, size, &config) != VP8_STATUS_OK) {
135 0 : outputData.clear(outputData.target);
136 0 : return false;
137 : }
138 :
139 100 : return true;
140 : }
141 :
142 : struct WebpStruct {
143 450 : static bool isWebpSupported(PixelFormat format) {
144 450 : switch (format) {
145 0 : case PixelFormat::A8:
146 : case PixelFormat::I8:
147 : case PixelFormat::IA88:
148 : case PixelFormat::Auto:
149 0 : log::error("Bitmap", "Webp supports only RGB888 and RGBA8888");
150 0 : return false;
151 450 : default:
152 450 : break;
153 : }
154 450 : return true;
155 : }
156 :
157 1223 : static int FileWriter(const uint8_t* data, size_t data_size, const WebPPicture* const pic) {
158 1223 : FILE* const out = (FILE *)pic->custom_ptr;
159 1223 : return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
160 : }
161 :
162 : FILE *fp = nullptr;
163 : BitmapWriter *out = nullptr;
164 :
165 : WebPConfig config;
166 : WebPPicture pic;
167 : WebPMemoryWriter writer;
168 :
169 : bool pictureInit = false;
170 : bool memoryInit = false;
171 : bool valid = true;
172 :
173 450 : ~WebpStruct() {
174 450 : if (pictureInit) {
175 450 : WebPPictureFree(&pic);
176 450 : pictureInit = false;
177 : }
178 :
179 450 : if (memoryInit) {
180 150 : WebPMemoryWriterClear(&writer);
181 150 : memoryInit = false;
182 : }
183 :
184 450 : if (fp) {
185 300 : fclose(fp);
186 : }
187 450 : }
188 :
189 450 : WebpStruct(bool lossless) {
190 450 : if (!WebPPictureInit(&pic)) {
191 0 : valid = false;
192 : }
193 :
194 450 : if (lossless) {
195 225 : if (!WebPConfigPreset(&config, WEBP_PRESET_ICON, 100.0f)) {
196 0 : valid = false;
197 : }
198 :
199 225 : config.lossless = 1;
200 225 : config.method = 6;
201 : } else {
202 225 : if (!WebPConfigPreset(&config, WEBP_PRESET_PICTURE, 90.0f)) {
203 0 : valid = false;
204 : }
205 :
206 225 : config.lossless = 0;
207 225 : config.method = 6;
208 : }
209 :
210 450 : if (!WebPValidateConfig(&config)) {
211 0 : valid = false;
212 : }
213 450 : }
214 :
215 150 : WebpStruct(BitmapWriter *v, bool lossless) : WebpStruct(lossless) {
216 150 : out = v;
217 150 : }
218 :
219 300 : WebpStruct(const StringView &filename, bool lossless) : WebpStruct(lossless) {
220 300 : fp = filesystem::native::fopen_fn(filename, "wb");
221 300 : if (!fp) {
222 0 : log::format(log::Error, "Bitmap", "fail to open file '%s' to write png data", filename.data());
223 0 : valid = false;
224 0 : return;
225 : }
226 0 : }
227 :
228 : explicit operator bool () const {
229 : return valid;
230 : }
231 :
232 450 : bool write(const uint8_t *data, BitmapWriter &state) {
233 450 : if (!valid) {
234 0 : return false;
235 : }
236 :
237 450 : if (!fp && !out) {
238 0 : return false;
239 : }
240 :
241 : WebPPicture pic;
242 450 : WebPPictureInit(&pic);
243 :
244 450 : pic.use_argb = 1;
245 450 : pic.width = state.width;
246 450 : pic.height = state.height;
247 :
248 450 : if (state.stride == 0) {
249 0 : state.stride = getBytesPerPixel(state.color) * state.width;
250 : }
251 :
252 450 : switch (state.color) {
253 0 : case PixelFormat::A8:
254 : case PixelFormat::I8:
255 : case PixelFormat::IA88:
256 : case PixelFormat::Auto:
257 0 : return false;
258 150 : case PixelFormat::RGB888:
259 150 : WebPPictureImportRGB(&pic, data, state.stride);
260 :
261 150 : break;
262 300 : case PixelFormat::RGBA8888:
263 300 : WebPPictureImportRGBA(&pic, data, state.stride);
264 300 : break;
265 : }
266 450 : pictureInit = true;
267 :
268 450 : if (fp) {
269 300 : pic.writer = FileWriter;
270 300 : pic.custom_ptr = fp;
271 : } else {
272 150 : WebPMemoryWriterInit(&writer);
273 150 : pic.writer = WebPMemoryWrite;
274 150 : pic.custom_ptr = &writer;
275 150 : memoryInit = true;
276 : }
277 :
278 450 : if (!WebPEncode(&config, &pic)) {
279 0 : return false;
280 : }
281 :
282 450 : if (out) {
283 150 : out->resize(out->target, writer.size);
284 150 : memcpy(out->getData(out->target, 0), writer.mem, writer.size);
285 : }
286 :
287 450 : return true;
288 : }
289 : };
290 :
291 150 : static bool saveWebpLossless(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
292 150 : if (!WebpStruct::isWebpSupported(state.color)) {
293 0 : return false;
294 : }
295 150 : if (invert) {
296 0 : log::error("Bitmap", "Inverted output is not supported for webp");
297 0 : return false;
298 : }
299 :
300 150 : WebpStruct coder(filename, true);
301 150 : return coder.write(data, state);
302 150 : }
303 :
304 75 : static bool writeWebpLossless(const uint8_t *data, BitmapWriter &state, bool invert) {
305 75 : if (!WebpStruct::isWebpSupported(state.color) || invert) {
306 0 : if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
307 0 : return false;
308 : }
309 :
310 75 : WebpStruct coder(&state, true);
311 75 : return coder.write(data, state);
312 75 : }
313 :
314 150 : static bool saveWebpLossy(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
315 150 : if (!WebpStruct::isWebpSupported(state.color) || invert) {
316 0 : if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
317 0 : return false;
318 : }
319 :
320 150 : WebpStruct coder(filename, false);
321 150 : return coder.write(data, state);
322 150 : }
323 :
324 75 : static bool writeWebpLossy(const uint8_t *data, BitmapWriter &state, bool invert) {
325 75 : if (!WebpStruct::isWebpSupported(state.color) || invert) {
326 0 : if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
327 0 : return false;
328 : }
329 :
330 75 : WebpStruct coder(&state, false);
331 75 : return coder.write(data, state);
332 75 : }
333 :
334 : }
|