/src/imagemagick/coders/qoi.c
Line | Count | Source |
1 | | /* |
2 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3 | | % % |
4 | | % % |
5 | | % % |
6 | | % QQQ OOO IIIII % |
7 | | % Q Q O O I % |
8 | | % Q Q O O I % |
9 | | % Q QQ O O I % |
10 | | % QQQQ OOO IIIII % |
11 | | % Q % |
12 | | % % |
13 | | % Quite OK Image Format % |
14 | | % % |
15 | | % Software Design % |
16 | | % Jules Maselbas % |
17 | | % December 2021 % |
18 | | % % |
19 | | % % |
20 | | % Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization % |
21 | | % dedicated to making software imaging solutions freely available. % |
22 | | % % |
23 | | % You may not use this file except in compliance with the License. You may % |
24 | | % obtain a copy of the License at % |
25 | | % % |
26 | | % https://imagemagick.org/license/ % |
27 | | % % |
28 | | % Unless required by applicable law or agreed to in writing, software % |
29 | | % distributed under the License is distributed on an "AS IS" BASIS, % |
30 | | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
31 | | % See the License for the specific language governing permissions and % |
32 | | % limitations under the License. % |
33 | | % % |
34 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
35 | | % |
36 | | % |
37 | | */ |
38 | | |
39 | | /* |
40 | | Include declarations. |
41 | | */ |
42 | | #include "MagickCore/studio.h" |
43 | | #include "MagickCore/blob.h" |
44 | | #include "MagickCore/blob-private.h" |
45 | | #include "MagickCore/constitute.h" |
46 | | #include "MagickCore/colorspace-private.h" |
47 | | #include "MagickCore/exception.h" |
48 | | #include "MagickCore/exception-private.h" |
49 | | #include "MagickCore/image.h" |
50 | | #include "MagickCore/image-private.h" |
51 | | #include "MagickCore/list.h" |
52 | | #include "MagickCore/magick.h" |
53 | | #include "MagickCore/memory_.h" |
54 | | #include "MagickCore/monitor.h" |
55 | | #include "MagickCore/monitor-private.h" |
56 | | #include "MagickCore/resource_.h" |
57 | | #include "MagickCore/quantum-private.h" |
58 | | #include "MagickCore/static.h" |
59 | | #include "MagickCore/string_.h" |
60 | | #include "MagickCore/module.h" |
61 | | |
62 | | /* |
63 | | Define declarations. |
64 | | */ |
65 | 929 | #define QOI_SRGB 0 |
66 | 365 | #define QOI_LINEAR 1 |
67 | | |
68 | 163k | #define QOI_OP_INDEX 0x00 /* 00xxxxxx */ |
69 | 53.5k | #define QOI_OP_DIFF 0x40 /* 01xxxxxx */ |
70 | 47.6k | #define QOI_OP_LUMA 0x80 /* 10xxxxxx */ |
71 | 74.9k | #define QOI_OP_RUN 0xc0 /* 11xxxxxx */ |
72 | 215k | #define QOI_OP_RGB 0xfe /* 11111110 */ |
73 | 153k | #define QOI_OP_RGBA 0xff /* 11111111 */ |
74 | | |
75 | 434k | #define QOI_MASK_2 0xc0 /* 11000000 */ |
76 | | |
77 | 299k | #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) |
78 | | |
79 | | /* |
80 | | Typedef declarations. |
81 | | */ |
82 | | typedef union |
83 | | { |
84 | | struct { unsigned char r, g, b, a; } rgba; |
85 | | unsigned int v; |
86 | | } qoi_rgba_t; |
87 | | |
88 | | /* |
89 | | Forward declarations. |
90 | | */ |
91 | | static MagickBooleanType |
92 | | WriteQOIImage(const ImageInfo *,Image *,ExceptionInfo *); |
93 | | |
94 | | /* |
95 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
96 | | % % |
97 | | % % |
98 | | % % |
99 | | % I s Q O I % |
100 | | % % |
101 | | % % |
102 | | % % |
103 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
104 | | % |
105 | | % IsQOI()() returns MagickTrue if the image format type, identified by the |
106 | | % magick string, is QOI. |
107 | | % |
108 | | % The format of the IsQOI method is: |
109 | | % |
110 | | % MagickBooleanType IsQOI(const unsigned char *magick,const size_t length) |
111 | | % |
112 | | % A description of each parameter follows: |
113 | | % |
114 | | % o magick: compare image format pattern against these bytes. |
115 | | % |
116 | | % o length: Specifies the length of the magick string. |
117 | | % |
118 | | */ |
119 | | static unsigned int IsQOI(const unsigned char *magick,const size_t length) |
120 | 0 | { |
121 | 0 | if (length < 4) |
122 | 0 | return(MagickFalse); |
123 | 0 | if (LocaleNCompare((const char *) magick, "qoif", 4) == 0) |
124 | 0 | return(MagickTrue); |
125 | 0 | return(MagickFalse); |
126 | 0 | } |
127 | | |
128 | | /* |
129 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
130 | | % % |
131 | | % % |
132 | | % % |
133 | | % R e a d Q O I I m a g e % |
134 | | % % |
135 | | % % |
136 | | % % |
137 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
138 | | % |
139 | | % ReadQOIImage() reads a Quite OK Image Format image file and returns it. It |
140 | | % allocates the memory necessary for the new Image structure and returns a |
141 | | % pointer to the new image. |
142 | | % |
143 | | % The format of the ReadQOIImage method is: |
144 | | % |
145 | | % Image *ReadQOIImage(image_info,ExceptionInfo *exception) |
146 | | % |
147 | | % A description of each parameter follows: |
148 | | % |
149 | | % o image_info: the image info. |
150 | | % |
151 | | % o exception: return any errors or warnings in this structure. |
152 | | % |
153 | | */ |
154 | | static Image *ReadQOIImage(const ImageInfo *image_info,ExceptionInfo *exception) |
155 | 807 | { |
156 | 807 | Image |
157 | 807 | *image; |
158 | | |
159 | 807 | Quantum |
160 | 807 | *q; |
161 | | |
162 | 807 | MagickBooleanType |
163 | 807 | status; |
164 | | |
165 | 807 | size_t |
166 | 807 | colorspace, |
167 | 807 | channels; |
168 | | |
169 | 807 | ssize_t |
170 | 807 | count, |
171 | 807 | i; |
172 | | |
173 | 807 | unsigned char |
174 | 807 | magick[4]; |
175 | | |
176 | 807 | qoi_rgba_t |
177 | 807 | px, |
178 | 807 | lut[64]; |
179 | | |
180 | 807 | int |
181 | 807 | b, |
182 | 807 | vg, |
183 | 807 | run; |
184 | | |
185 | | /* |
186 | | Open image file. |
187 | | */ |
188 | 807 | assert(image_info != (const ImageInfo *) NULL); |
189 | 807 | assert(image_info->signature == MagickCoreSignature); |
190 | 807 | assert(exception != (ExceptionInfo *) NULL); |
191 | 807 | assert(exception->signature == MagickCoreSignature); |
192 | 807 | if (IsEventLogging() != MagickFalse) |
193 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", |
194 | 0 | image_info->filename); |
195 | 807 | image=AcquireImage(image_info,exception); |
196 | 807 | status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
197 | 807 | if (status == MagickFalse) |
198 | 0 | { |
199 | 0 | image=DestroyImageList(image); |
200 | 0 | return((Image *) NULL); |
201 | 0 | } |
202 | | /* |
203 | | Initialize image structure. |
204 | | */ |
205 | 807 | image->endian=MSBEndian; |
206 | 807 | image->depth=8; |
207 | 807 | count=ReadBlob(image,4,magick); |
208 | 807 | if ((count != 4) || (LocaleNCompare((char *) magick, "qoif", 4) != 0)) |
209 | 782 | ThrowReaderException(CorruptImageError,"ImproperImageHeader"); |
210 | 782 | image->columns=(size_t) ReadBlobMSBLong(image); |
211 | 782 | image->rows=(size_t) ReadBlobMSBLong(image); |
212 | 782 | if (image->columns == 0 || image->rows == 0) |
213 | 745 | ThrowReaderException(CorruptImageError,"NegativeOrZeroImageSize"); |
214 | 745 | channels=(size_t) ReadBlobByte(image); |
215 | 745 | if (channels == 3) |
216 | 408 | SetQuantumImageType(image,RGBQuantum); |
217 | 337 | else if (channels == 4) |
218 | 263 | { |
219 | 263 | SetQuantumImageType(image,RGBAQuantum); |
220 | 263 | image->alpha_trait=BlendPixelTrait; |
221 | 263 | } |
222 | 74 | else |
223 | 671 | ThrowReaderException(CorruptImageError,"ImproperImageHeader"); |
224 | 671 | colorspace=(size_t) ReadBlobByte(image); |
225 | 671 | if (colorspace == QOI_SRGB) |
226 | 439 | (void) SetImageColorspace(image,sRGBColorspace,exception); |
227 | 232 | else if (colorspace == QOI_LINEAR) |
228 | 222 | (void) SetImageColorspace(image,RGBColorspace,exception); |
229 | 10 | else |
230 | 661 | ThrowReaderException(CorruptImageError,"ImproperImageHeader"); |
231 | 661 | if (image->ping != MagickFalse) |
232 | 2 | { |
233 | 2 | (void) CloseBlob(image); |
234 | 2 | return(GetFirstImageInList(image)); |
235 | 2 | } |
236 | 659 | status=SetImageExtent(image,image->columns,image->rows,exception); |
237 | 659 | if (status == MagickFalse) |
238 | 86 | return(DestroyImageList(image)); |
239 | | /* |
240 | | Get a write pointer for the whole image. |
241 | | */ |
242 | 573 | q=QueueAuthenticPixels(image,0,0,image->columns,image->rows,exception); |
243 | 573 | if (q == (Quantum *) NULL) |
244 | 573 | ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); |
245 | | /* |
246 | | Initialize decoding state. |
247 | | */ |
248 | 573 | px.rgba.r=0; |
249 | 573 | px.rgba.g=0; |
250 | 573 | px.rgba.b=0; |
251 | 573 | px.rgba.a=255; |
252 | 573 | memset(lut,0,sizeof(lut)); |
253 | | /* |
254 | | Actual decoding. |
255 | | */ |
256 | 154k | for (i=0; i < (ssize_t) (image->rows * image->columns);) |
257 | 154k | { |
258 | 154k | run = 0; |
259 | | |
260 | 154k | if ((b=ReadBlobByte(image)) == EOF) |
261 | 130 | break; |
262 | 154k | if (b == QOI_OP_RGB) |
263 | 669 | { |
264 | 669 | if ((b=ReadBlobByte(image)) == EOF) |
265 | 2 | break; |
266 | 667 | px.rgba.r=(unsigned char) b; |
267 | 667 | if ((b=ReadBlobByte(image)) == EOF) |
268 | 9 | break; |
269 | 658 | px.rgba.g=(unsigned char) b; |
270 | 658 | if ((b=ReadBlobByte(image)) == EOF) |
271 | 9 | break; |
272 | 649 | px.rgba.b=(unsigned char) b; |
273 | 649 | } |
274 | 153k | else if (b == QOI_OP_RGBA) |
275 | 4.10k | { |
276 | 4.10k | if ((b=ReadBlobByte(image)) == EOF) |
277 | 3 | break; |
278 | 4.10k | px.rgba.r=(unsigned char) b; |
279 | 4.10k | if ((b=ReadBlobByte(image)) == EOF) |
280 | 9 | break; |
281 | 4.09k | px.rgba.g=(unsigned char) b; |
282 | 4.09k | if ((b=ReadBlobByte(image)) == EOF) |
283 | 10 | break; |
284 | 4.08k | px.rgba.b=(unsigned char) b; |
285 | 4.08k | if ((b=ReadBlobByte(image)) == EOF) |
286 | 9 | break; |
287 | 4.07k | px.rgba.a=(unsigned char) b; |
288 | 4.07k | } |
289 | 149k | else if ((b & QOI_MASK_2) == QOI_OP_INDEX) |
290 | 97.8k | { |
291 | 97.8k | px=lut[b & ~QOI_MASK_2]; |
292 | 97.8k | } |
293 | 51.3k | else if ((b & QOI_MASK_2) == QOI_OP_DIFF) |
294 | 5.47k | { |
295 | 5.47k | px.rgba.r+=((b >> 4) & 0x03) - 2; |
296 | 5.47k | px.rgba.g+=((b >> 2) & 0x03) - 2; |
297 | 5.47k | px.rgba.b+=( b & 0x03) - 2; |
298 | 5.47k | } |
299 | 45.8k | else if ((b & QOI_MASK_2) == QOI_OP_LUMA) |
300 | 1.82k | { |
301 | 1.82k | vg=(b & ~QOI_MASK_2) - 32; |
302 | 1.82k | if ((b=ReadBlobByte(image)) == EOF) |
303 | 1 | break; |
304 | 1.82k | px.rgba.r+=(unsigned char) (vg - 8 + ((b >> 4) & 0x0f)); |
305 | 1.82k | px.rgba.g+=(unsigned char) vg; |
306 | 1.82k | px.rgba.b+=(unsigned char) (vg - 8 + (b & 0x0f)); |
307 | 1.82k | } |
308 | 44.0k | else if ((b & QOI_MASK_2) == QOI_OP_RUN) |
309 | 44.0k | { |
310 | 44.0k | run=b & ~QOI_MASK_2; |
311 | 44.0k | } |
312 | 153k | lut[QOI_COLOR_HASH(px) % 64]=px; |
313 | 153k | do |
314 | 2.78M | { |
315 | 2.78M | if (i < (ssize_t) (image->columns*image->rows)) |
316 | 2.78M | { |
317 | 2.78M | SetPixelRed(image,ScaleCharToQuantum((unsigned char)px.rgba.r),q); |
318 | 2.78M | SetPixelGreen(image,ScaleCharToQuantum((unsigned char)px.rgba.g),q); |
319 | 2.78M | SetPixelBlue(image,ScaleCharToQuantum((unsigned char)px.rgba.b),q); |
320 | 2.78M | if (channels == 4) |
321 | 652k | SetPixelAlpha(image,ScaleCharToQuantum((unsigned char) px.rgba.a),q); |
322 | 2.78M | } |
323 | 2.78M | q+=(ptrdiff_t) GetPixelChannels(image); |
324 | 2.78M | i++; |
325 | 2.78M | } while (run-- > 0); |
326 | 153k | status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) i, |
327 | 153k | image->rows * image->columns); |
328 | 153k | if (status == MagickFalse) |
329 | 0 | break; |
330 | 153k | } |
331 | 573 | status=SyncAuthenticPixels(image,exception); |
332 | 573 | if (i < (ssize_t) (image->columns*image->rows)) |
333 | 391 | ThrowReaderException(CorruptImageError,"NotEnoughPixelData"); |
334 | 391 | if (EOFBlob(image) != MagickFalse) |
335 | 0 | ThrowFileException(exception,CorruptImageError, |
336 | 391 | "UnexpectedEndOfFile",image->filename); |
337 | 391 | if (CloseBlob(image) == MagickFalse) |
338 | 0 | status=MagickFalse; |
339 | 391 | if (status == MagickFalse) |
340 | 0 | return(DestroyImageList(image)); |
341 | 391 | return(GetFirstImageInList(image)); |
342 | 391 | } |
343 | | |
344 | | /* |
345 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
346 | | % % |
347 | | % % |
348 | | % % |
349 | | % R e g i s t e r Q O I I m a g e % |
350 | | % % |
351 | | % % |
352 | | % % |
353 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
354 | | % |
355 | | % RegisterQOIImage() adds properties for the QOI image format to |
356 | | % the list of supported formats. The properties include the image format |
357 | | % tag, a method to read and/or write the format, whether the format |
358 | | % supports the saving of more than one frame to the same file or blob, |
359 | | % whether the format supports native in-memory I/O, and a brief |
360 | | % description of the format. |
361 | | % |
362 | | % The format of the RegisterQOIImage method is: |
363 | | % |
364 | | % size_t RegisterQOIImage(void) |
365 | | % |
366 | | */ |
367 | | ModuleExport size_t RegisterQOIImage(void) |
368 | 9 | { |
369 | 9 | MagickInfo |
370 | 9 | *entry; |
371 | | |
372 | 9 | entry=AcquireMagickInfo("QOI","QOI","Quite OK image format"); |
373 | 9 | entry->decoder=(DecodeImageHandler *) ReadQOIImage; |
374 | 9 | entry->encoder=(EncodeImageHandler *) WriteQOIImage; |
375 | 9 | entry->magick=(IsImageFormatHandler *) IsQOI; |
376 | 9 | entry->flags^=CoderAdjoinFlag; |
377 | 9 | (void) RegisterMagickInfo(entry); |
378 | 9 | return(MagickImageCoderSignature); |
379 | 9 | } |
380 | | |
381 | | /* |
382 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
383 | | % % |
384 | | % % |
385 | | % % |
386 | | % U n r e g i s t e r Q O I I m a g e % |
387 | | % % |
388 | | % % |
389 | | % % |
390 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
391 | | % |
392 | | % UnregisterQOIImage() removes format registrations made by the |
393 | | % QOI module from the list of supported formats. |
394 | | % |
395 | | % The format of the UnregisterQOIImage method is: |
396 | | % |
397 | | % UnregisterQOIImage(void) |
398 | | % |
399 | | */ |
400 | | ModuleExport void UnregisterQOIImage(void) |
401 | 0 | { |
402 | 0 | (void) UnregisterMagickInfo("QOI"); |
403 | 0 | } |
404 | | |
405 | | /* |
406 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
407 | | % % |
408 | | % % |
409 | | % % |
410 | | % W r i t e Q O I I m a g e % |
411 | | % % |
412 | | % % |
413 | | % % |
414 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
415 | | % |
416 | | % WriteQOIImage() writes an image in the Quite OK Image Format image format. |
417 | | % |
418 | | % The format of the WriteQOIImage method is: |
419 | | % |
420 | | % MagickBooleanType WriteQOIImage(const ImageInfo *image_info, |
421 | | % Image *image,ExceptionInfo *exception) |
422 | | % |
423 | | % A description of each parameter follows. |
424 | | % |
425 | | % o image_info: the image info. |
426 | | % |
427 | | % o image: The image. |
428 | | % |
429 | | % o exception: return any errors or warnings in this structure. |
430 | | % |
431 | | */ |
432 | | static MagickBooleanType WriteQOIImage(const ImageInfo *image_info,Image *image, |
433 | | ExceptionInfo *exception) |
434 | 391 | { |
435 | 391 | const Quantum |
436 | 391 | *p; |
437 | | |
438 | 391 | MagickBooleanType |
439 | 391 | status; |
440 | | |
441 | 391 | qoi_rgba_t |
442 | 391 | px, |
443 | 391 | pp, |
444 | 391 | lut[64]; |
445 | | |
446 | 391 | QuantumType |
447 | 391 | quantum_type; |
448 | | |
449 | 391 | size_t |
450 | 391 | channels, |
451 | 391 | colorspace, |
452 | 391 | end, |
453 | 391 | run; |
454 | | |
455 | 391 | ssize_t |
456 | 391 | idx, |
457 | 391 | i; |
458 | | |
459 | | /* |
460 | | Open output image file. |
461 | | */ |
462 | 391 | assert(image_info != (const ImageInfo *) NULL); |
463 | 391 | assert(image_info->signature == MagickCoreSignature); |
464 | 391 | assert(image != (Image *) NULL); |
465 | 391 | assert(image->signature == MagickCoreSignature); |
466 | 391 | assert(exception != (ExceptionInfo *) NULL); |
467 | 391 | assert(exception->signature == MagickCoreSignature); |
468 | 391 | if (IsEventLogging() != MagickFalse) |
469 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
470 | 391 | status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); |
471 | 391 | if (status == MagickFalse) |
472 | 0 | return(status); |
473 | 391 | image->endian=MSBEndian; |
474 | 391 | image->depth=8; |
475 | 391 | if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) |
476 | 0 | (void) TransformImageColorspace(image,sRGBColorspace,exception); |
477 | 391 | if (IsRGBColorspace(image->colorspace) != MagickFalse) |
478 | 133 | colorspace=QOI_LINEAR; |
479 | 258 | else |
480 | 258 | colorspace=QOI_SRGB; |
481 | 391 | quantum_type=GetQuantumType(image,exception); |
482 | 391 | if ((quantum_type == RGBQuantum) || (quantum_type == GrayQuantum) || |
483 | 160 | (quantum_type == IndexQuantum)) |
484 | 231 | channels=3; |
485 | 160 | else |
486 | 160 | if ((quantum_type == RGBAQuantum) || (quantum_type == GrayAlphaQuantum) || |
487 | 0 | (quantum_type == IndexAlphaQuantum)) |
488 | 160 | channels=4; |
489 | 0 | else |
490 | 391 | ThrowWriterException(ImageError,"ImageTypeNotSupported"); |
491 | | /* |
492 | | Write QOI header. |
493 | | */ |
494 | 391 | (void) WriteBlobString(image,"qoif"); |
495 | 391 | (void) WriteBlobMSBLong(image,(unsigned int) image->columns); |
496 | 391 | (void) WriteBlobMSBLong(image,(unsigned int) image->rows); |
497 | 391 | (void) WriteBlobByte(image,(unsigned char) channels); |
498 | 391 | (void) WriteBlobByte(image,(unsigned char) colorspace); |
499 | | /* |
500 | | Initialize encoding state. |
501 | | */ |
502 | 391 | px.rgba.r=0; |
503 | 391 | px.rgba.g=0; |
504 | 391 | px.rgba.b=0; |
505 | 391 | px.rgba.a=255; |
506 | 391 | memset(lut,0,sizeof(lut)); |
507 | 391 | run=0; |
508 | 391 | p=GetVirtualPixels(image,0,0,image->columns,image->rows,exception); |
509 | 391 | if (p == (const Quantum *) NULL) |
510 | 391 | ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); |
511 | | /* |
512 | | Do the actual encoding. |
513 | | */ |
514 | 391 | end=image->rows * image->columns; |
515 | 1.89M | for (i=0; i < (ssize_t) end; i++) { |
516 | 1.89M | pp=px; |
517 | 1.89M | px.rgba.r=ScaleQuantumToChar(GetPixelRed(image,p)); |
518 | 1.89M | px.rgba.g=ScaleQuantumToChar(GetPixelGreen(image,p)); |
519 | 1.89M | px.rgba.b=ScaleQuantumToChar(GetPixelBlue(image,p)); |
520 | 1.89M | if (channels == 4) |
521 | 577k | px.rgba.a=ScaleQuantumToChar(GetPixelAlpha(image,p)); |
522 | 1.89M | p+=(ptrdiff_t) GetPixelChannels(image); |
523 | | |
524 | 1.89M | if (pp.v == px.v) |
525 | 1.81M | { |
526 | 1.81M | run++; |
527 | 1.81M | if (run == 62) |
528 | 27.5k | { |
529 | 27.5k | (void) WriteBlobByte(image,QOI_OP_RUN | (unsigned char) (run - 1)); |
530 | 27.5k | run=0; |
531 | 27.5k | } |
532 | 1.81M | continue; |
533 | 1.81M | } |
534 | 79.8k | if (run > 0) |
535 | 3.07k | { |
536 | 3.07k | (void) WriteBlobByte(image,QOI_OP_RUN | (unsigned char) (run - 1)); |
537 | 3.07k | run=0; |
538 | 3.07k | } |
539 | 79.8k | idx=QOI_COLOR_HASH(px) % 64; |
540 | 79.8k | if (lut[idx].v == px.v) |
541 | 14.5k | { |
542 | 14.5k | (void) WriteBlobByte(image,QOI_OP_INDEX | (unsigned char) idx); |
543 | 14.5k | continue; |
544 | 14.5k | } |
545 | 65.3k | lut[QOI_COLOR_HASH(px) % 64]=px; |
546 | 65.3k | if (pp.rgba.a == px.rgba.a) |
547 | 64.9k | { |
548 | 64.9k | signed char |
549 | 64.9k | vr, |
550 | 64.9k | vg, |
551 | 64.9k | vb, |
552 | 64.9k | vg_r, |
553 | 64.9k | vg_b; |
554 | | |
555 | 64.9k | unsigned char |
556 | 64.9k | diff, |
557 | 64.9k | luma; |
558 | | |
559 | 64.9k | vr=(signed char) (px.rgba.r - pp.rgba.r); |
560 | 64.9k | vg=(signed char) (px.rgba.g - pp.rgba.g); |
561 | 64.9k | vb=(signed char) (px.rgba.b - pp.rgba.b); |
562 | 64.9k | vg_r=(signed char) (vr - vg); |
563 | 64.9k | vg_b=(signed char) (vb - vg); |
564 | | |
565 | 64.9k | if ((vr > -3) && (vr < 2) && (vg > -3) && (vg < 2) && |
566 | 5.63k | (vb > -3) && (vb < 2)) |
567 | 2.15k | { |
568 | 2.15k | diff=(unsigned char) ((vr + 2) << 4 | (vg + 2) << 2 | (vb + 2)); |
569 | 2.15k | (void) WriteBlobByte(image,QOI_OP_DIFF | diff); |
570 | 2.15k | } |
571 | 62.8k | else if ((vg_r > -9) && (vg_r < 8) && (vg > -33) && (vg < 32) && |
572 | 6.57k | (vg_b > -9) && (vg_b < 8)) |
573 | 1.78k | { |
574 | 1.78k | luma=(unsigned char) (vg + 32); |
575 | 1.78k | (void) WriteBlobByte(image,QOI_OP_LUMA | luma); |
576 | 1.78k | luma=(unsigned char) ((vg_r + 8) << 4 | (vg_b + 8)); |
577 | 1.78k | (void) WriteBlobByte(image,luma); |
578 | 1.78k | } |
579 | 61.0k | else |
580 | 61.0k | { |
581 | 61.0k | (void) WriteBlobByte(image,QOI_OP_RGB); |
582 | 61.0k | (void) WriteBlobByte(image,px.rgba.r); |
583 | 61.0k | (void) WriteBlobByte(image,px.rgba.g); |
584 | 61.0k | (void) WriteBlobByte(image,px.rgba.b); |
585 | 61.0k | } |
586 | 64.9k | } |
587 | 341 | else |
588 | 341 | { |
589 | 341 | (void) WriteBlobByte(image,QOI_OP_RGBA); |
590 | 341 | (void) WriteBlobByte(image,px.rgba.r); |
591 | 341 | (void) WriteBlobByte(image,px.rgba.g); |
592 | 341 | (void) WriteBlobByte(image,px.rgba.b); |
593 | 341 | (void) WriteBlobByte(image,px.rgba.a); |
594 | 341 | } |
595 | 65.3k | } |
596 | 391 | if (run > 0) |
597 | 214 | (void) WriteBlobByte(image,QOI_OP_RUN | (unsigned char) (run - 1)); |
598 | | /* |
599 | | Write the QOI end marker: seven 0x00 bytes followed by 0x01. |
600 | | */ |
601 | 3.12k | for (i=0; i < 7; i++) |
602 | 2.73k | (void) WriteBlobByte(image,0x00); |
603 | 391 | (void) WriteBlobByte(image,0x01); |
604 | 391 | if (CloseBlob(image) == MagickFalse) |
605 | 0 | status=MagickFalse; |
606 | 391 | return(status); |
607 | 391 | } |