Coverage Report

Created: 2026-09-13 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}