1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                            Y   Y  U   U  V   V                              %
7 %                             Y Y   U   U  V   V                              %
8 %                              Y    U   U  V   V                              %
9 %                              Y    U   U   V V                               %
10 %                              Y     UUU     V                                %
11 %                                                                             %
12 %                                                                             %
13 %            Read/Write Raw CCIR 601 4:1:1 or 4:2:2 Image Format              %
14 %                                                                             %
15 %                              Software Design                                %
16 %                                   Cristy                                    %
17 %                                 July 1992                                   %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2021 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/script/license.php                               %
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/cache.h"
46 #include "MagickCore/colorspace.h"
47 #include "MagickCore/constitute.h"
48 #include "MagickCore/exception.h"
49 #include "MagickCore/exception-private.h"
50 #include "MagickCore/geometry.h"
51 #include "MagickCore/image.h"
52 #include "MagickCore/image-private.h"
53 #include "MagickCore/list.h"
54 #include "MagickCore/magick.h"
55 #include "MagickCore/memory_.h"
56 #include "MagickCore/monitor.h"
57 #include "MagickCore/monitor-private.h"
58 #include "MagickCore/pixel-accessor.h"
59 #include "MagickCore/resize.h"
60 #include "MagickCore/quantum-private.h"
61 #include "MagickCore/static.h"
62 #include "MagickCore/string_.h"
63 #include "MagickCore/module.h"
64 #include "MagickCore/utility.h"
65 
66 /*
67   Forward declarations.
68 */
69 static MagickBooleanType
70   WriteYUVImage(const ImageInfo *,Image *,ExceptionInfo *);
71 
72 /*
73 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
74 %                                                                             %
75 %                                                                             %
76 %                                                                             %
77 %   R e a d Y U V I m a g e                                                   %
78 %                                                                             %
79 %                                                                             %
80 %                                                                             %
81 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 %
83 %  ReadYUVImage() reads an image with digital YUV (CCIR 601 4:1:1, plane
84 %  or partition interlaced, or 4:2:2 plane, partition interlaced or
85 %  noninterlaced) bytes and returns it.  It allocates the memory necessary
86 %  for the new Image structure and returns a pointer to the new image.
87 %
88 %  The format of the ReadYUVImage method is:
89 %
90 %      Image *ReadYUVImage(const ImageInfo *image_info,ExceptionInfo *exception)
91 %
92 %  A description of each parameter follows:
93 %
94 %    o image_info: the image info.
95 %
96 %    o exception: return any errors or warnings in this structure.
97 %
98 */
ReadYUVImage(const ImageInfo * image_info,ExceptionInfo * exception)99 static Image *ReadYUVImage(const ImageInfo *image_info,ExceptionInfo *exception)
100 {
101   Image
102     *chroma_image,
103     *image,
104     *resize_image;
105 
106   InterlaceType
107     interlace;
108 
109   MagickBooleanType
110     status;
111 
112   ssize_t
113     x;
114 
115   Quantum
116     *q;
117 
118   unsigned char
119     *p;
120 
121   ssize_t
122     count,
123     horizontal_factor,
124     vertical_factor,
125     y;
126 
127   size_t
128     length,
129     quantum;
130 
131   unsigned char
132     *scanline;
133 
134   /*
135     Allocate image structure.
136   */
137   assert(image_info != (const ImageInfo *) NULL);
138   assert(image_info->signature == MagickCoreSignature);
139   if (image_info->debug != MagickFalse)
140     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
141       image_info->filename);
142   assert(exception != (ExceptionInfo *) NULL);
143   assert(exception->signature == MagickCoreSignature);
144   image=AcquireImage(image_info,exception);
145   if ((image->columns == 0) || (image->rows == 0))
146     ThrowReaderException(OptionError,"MustSpecifyImageSize");
147   status=SetImageExtent(image,image->columns,image->rows,exception);
148   if (status == MagickFalse)
149     return(DestroyImageList(image));
150   quantum=(ssize_t) (image->depth <= 8 ? 1 : 2);
151   interlace=image_info->interlace;
152   horizontal_factor=2;
153   vertical_factor=2;
154   if (image_info->sampling_factor != (char *) NULL)
155     {
156       GeometryInfo
157         geometry_info;
158 
159       MagickStatusType
160         flags;
161 
162       flags=ParseGeometry(image_info->sampling_factor,&geometry_info);
163       horizontal_factor=(ssize_t) geometry_info.rho;
164       vertical_factor=(ssize_t) geometry_info.sigma;
165       if ((flags & SigmaValue) == 0)
166         vertical_factor=horizontal_factor;
167       if ((horizontal_factor != 1) && (horizontal_factor != 2) &&
168           (vertical_factor != 1) && (vertical_factor != 2))
169         ThrowReaderException(CorruptImageError,"UnexpectedSamplingFactor");
170     }
171   if ((interlace == UndefinedInterlace) ||
172       ((interlace == NoInterlace) && (vertical_factor == 2)))
173     {
174       interlace=NoInterlace;    /* CCIR 4:2:2 */
175       if (vertical_factor == 2)
176         interlace=PlaneInterlace; /* CCIR 4:1:1 */
177     }
178   if (interlace != PartitionInterlace)
179     {
180       /*
181         Open image file.
182       */
183       status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
184       if (status == MagickFalse)
185         {
186           image=DestroyImageList(image);
187           return((Image *) NULL);
188         }
189       if (DiscardBlobBytes(image,(MagickSizeType) image->offset) == MagickFalse)
190         ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
191           image->filename);
192     }
193   /*
194     Allocate memory for a scanline.
195   */
196   if (interlace == NoInterlace)
197     scanline=(unsigned char *) AcquireQuantumMemory((size_t) (2UL*
198       image->columns+2UL),(size_t) quantum*sizeof(*scanline));
199   else
200     scanline=(unsigned char *) AcquireQuantumMemory(image->columns,
201       (size_t) quantum*sizeof(*scanline));
202   if (scanline == (unsigned char *) NULL)
203     ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
204   status=MagickTrue;
205   do
206   {
207     chroma_image=CloneImage(image,(image->columns+horizontal_factor-1)/
208       horizontal_factor,(image->rows+vertical_factor-1)/vertical_factor,
209       MagickTrue,exception);
210     if (chroma_image == (Image *) NULL)
211       {
212         scanline=(unsigned char *) RelinquishMagickMemory(scanline);
213         ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
214       }
215     /*
216       Convert raster image to pixel packets.
217     */
218     if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
219       if (image->scene >= (image_info->scene+image_info->number_scenes-1))
220         break;
221     status=SetImageExtent(image,image->columns,image->rows,exception);
222     if (status == MagickFalse)
223       break;
224     if (interlace == PartitionInterlace)
225       {
226         AppendImageFormat("Y",image->filename);
227         status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
228         if (status == MagickFalse)
229           {
230             scanline=(unsigned char *) RelinquishMagickMemory(scanline);
231             image=DestroyImageList(image);
232             return((Image *) NULL);
233           }
234       }
235     for (y=0; y < (ssize_t) image->rows; y++)
236     {
237       Quantum
238         *chroma_pixels;
239 
240       if (interlace == NoInterlace)
241         {
242           if ((y > 0) || (GetPreviousImageInList(image) == (Image *) NULL))
243             {
244               length=2*quantum*image->columns;
245               count=ReadBlob(image,length,scanline);
246               if (count != (ssize_t) length)
247                 {
248                   status=MagickFalse;
249                   ThrowFileException(exception,CorruptImageError,
250                     "UnexpectedEndOfFile",image->filename);
251                   break;
252                 }
253             }
254           p=scanline;
255           q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
256           if (q == (Quantum *) NULL)
257             break;
258           chroma_pixels=QueueAuthenticPixels(chroma_image,0,y,
259             chroma_image->columns,1,exception);
260           if (chroma_pixels == (Quantum *) NULL)
261             break;
262           for (x=0; x < (ssize_t) image->columns; x+=2)
263           {
264             SetPixelRed(chroma_image,0,chroma_pixels);
265             if (quantum == 1)
266               SetPixelGreen(chroma_image,ScaleCharToQuantum(*p++),
267                 chroma_pixels);
268             else
269               {
270                 SetPixelGreen(chroma_image,ScaleShortToQuantum(((*p) << 8) |
271                   *(p+1)),chroma_pixels);
272                 p+=2;
273               }
274             if (quantum == 1)
275               SetPixelRed(image,ScaleCharToQuantum(*p++),q);
276             else
277               {
278                 SetPixelRed(image,ScaleShortToQuantum(((*p) << 8) | *(p+1)),q);
279                 p+=2;
280               }
281             SetPixelGreen(image,0,q);
282             SetPixelBlue(image,0,q);
283             q+=GetPixelChannels(image);
284             SetPixelGreen(image,0,q);
285             SetPixelBlue(image,0,q);
286             if (quantum == 1)
287               SetPixelBlue(chroma_image,ScaleCharToQuantum(*p++),chroma_pixels);
288             else
289               {
290                 SetPixelBlue(chroma_image,ScaleShortToQuantum(((*p) << 8) |
291                   *(p+1)),chroma_pixels);
292                 p+=2;
293               }
294             if (quantum == 1)
295               SetPixelRed(image,ScaleCharToQuantum(*p++),q);
296             else
297               {
298                 SetPixelRed(image,ScaleShortToQuantum(((*p) << 8) | *(p+1)),q);
299                 p+=2;
300               }
301             chroma_pixels+=GetPixelChannels(chroma_image);
302             q+=GetPixelChannels(image);
303           }
304         }
305       else
306         {
307           if ((y > 0) || (GetPreviousImageInList(image) == (Image *) NULL))
308             {
309               length=quantum*image->columns;
310               count=ReadBlob(image,length,scanline);
311               if (count != (ssize_t) length)
312                 {
313                   status=MagickFalse;
314                   ThrowFileException(exception,CorruptImageError,
315                     "UnexpectedEndOfFile",image->filename);
316                   break;
317                 }
318             }
319           p=scanline;
320           q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
321           if (q == (Quantum *) NULL)
322             break;
323           for (x=0; x < (ssize_t) image->columns; x++)
324           {
325             if (quantum == 1)
326               SetPixelRed(image,ScaleCharToQuantum(*p++),q);
327             else
328               {
329                 SetPixelRed(image,ScaleShortToQuantum(((*p) << 8) | *(p+1)),q);
330                 p+=2;
331               }
332             SetPixelGreen(image,0,q);
333             SetPixelBlue(image,0,q);
334             q+=GetPixelChannels(image);
335           }
336         }
337       if (SyncAuthenticPixels(image,exception) == MagickFalse)
338         break;
339       if (interlace == NoInterlace)
340         if (SyncAuthenticPixels(chroma_image,exception) == MagickFalse)
341           break;
342       if (image->previous == (Image *) NULL)
343         {
344           status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
345             image->rows);
346           if (status == MagickFalse)
347             break;
348         }
349     }
350     if (interlace == PartitionInterlace)
351       {
352         (void) CloseBlob(image);
353         AppendImageFormat("U",image->filename);
354         status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
355         if (status == MagickFalse)
356           {
357             scanline=(unsigned char *) RelinquishMagickMemory(scanline);
358             image=DestroyImageList(image);
359             return((Image *) NULL);
360           }
361       }
362     if (interlace != NoInterlace)
363       {
364         for (y=0; y < (ssize_t) chroma_image->rows; y++)
365         {
366           length=quantum*chroma_image->columns;
367           count=ReadBlob(image,length,scanline);
368           if (count != (ssize_t) length)
369             {
370               status=MagickFalse;
371               ThrowFileException(exception,CorruptImageError,
372                 "UnexpectedEndOfFile",image->filename);
373               break;
374             }
375           p=scanline;
376           q=QueueAuthenticPixels(chroma_image,0,y,chroma_image->columns,1,
377             exception);
378           if (q == (Quantum *) NULL)
379             break;
380           for (x=0; x < (ssize_t) chroma_image->columns; x++)
381           {
382             SetPixelRed(chroma_image,0,q);
383             if (quantum == 1)
384               SetPixelGreen(chroma_image,ScaleCharToQuantum(*p++),q);
385             else
386               {
387                 SetPixelGreen(chroma_image,ScaleShortToQuantum(((*p) << 8) |
388                   *(p+1)),q);
389                 p+=2;
390               }
391             SetPixelBlue(chroma_image,0,q);
392             q+=GetPixelChannels(chroma_image);
393           }
394           if (SyncAuthenticPixels(chroma_image,exception) == MagickFalse)
395             break;
396         }
397       if (interlace == PartitionInterlace)
398         {
399           (void) CloseBlob(image);
400           AppendImageFormat("V",image->filename);
401           status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
402           if (status == MagickFalse)
403             {
404               scanline=(unsigned char *) RelinquishMagickMemory(scanline);
405               image=DestroyImageList(image);
406               return((Image *) NULL);
407             }
408         }
409       for (y=0; y < (ssize_t) chroma_image->rows; y++)
410       {
411         length=quantum*chroma_image->columns;
412         count=ReadBlob(image,length,scanline);
413         if (count != (ssize_t) length)
414           {
415             status=MagickFalse;
416             ThrowFileException(exception,CorruptImageError,
417               "UnexpectedEndOfFile",image->filename);
418             break;
419           }
420         p=scanline;
421         q=GetAuthenticPixels(chroma_image,0,y,chroma_image->columns,1,
422           exception);
423         if (q == (Quantum *) NULL)
424           break;
425         for (x=0; x < (ssize_t) chroma_image->columns; x++)
426         {
427           if (quantum == 1)
428             SetPixelBlue(chroma_image,ScaleCharToQuantum(*p++),q);
429           else
430             {
431               SetPixelBlue(chroma_image,ScaleShortToQuantum(((*p) << 8) |
432                 *(p+1)),q);
433               p+=2;
434             }
435           q+=GetPixelChannels(chroma_image);
436         }
437         if (SyncAuthenticPixels(chroma_image,exception) == MagickFalse)
438           break;
439       }
440     }
441     /*
442       Scale image.
443     */
444     resize_image=ResizeImage(chroma_image,image->columns,image->rows,
445       TriangleFilter,exception);
446     chroma_image=DestroyImage(chroma_image);
447     if (resize_image == (Image *) NULL)
448       {
449         scanline=(unsigned char *) RelinquishMagickMemory(scanline);
450         ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
451       }
452     for (y=0; y < (ssize_t) image->rows; y++)
453     {
454       const Quantum
455         *chroma_pixels;
456 
457       q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
458       chroma_pixels=GetVirtualPixels(resize_image,0,y,resize_image->columns,1,
459         exception);
460       if ((q == (Quantum *) NULL) ||
461           (chroma_pixels == (const Quantum *) NULL))
462         break;
463       for (x=0; x < (ssize_t) image->columns; x++)
464       {
465         SetPixelGreen(image,GetPixelGreen(resize_image,chroma_pixels),q);
466         SetPixelBlue(image,GetPixelBlue(resize_image,chroma_pixels),q);
467         chroma_pixels+=GetPixelChannels(resize_image);
468         q+=GetPixelChannels(image);
469       }
470       if (SyncAuthenticPixels(image,exception) == MagickFalse)
471         break;
472     }
473     resize_image=DestroyImage(resize_image);
474     if (SetImageColorspace(image,YCbCrColorspace,exception) == MagickFalse)
475       break;
476     if (interlace == PartitionInterlace)
477       (void) CopyMagickString(image->filename,image_info->filename,
478         MagickPathExtent);
479     if (EOFBlob(image) != MagickFalse)
480       {
481         ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
482           image->filename);
483         break;
484       }
485     /*
486       Proceed to next image.
487     */
488     if (image_info->number_scenes != 0)
489       if (image->scene >= (image_info->scene+image_info->number_scenes-1))
490         break;
491     if (interlace == NoInterlace)
492       count=ReadBlob(image,(size_t) (2*quantum*image->columns),scanline);
493     else
494       count=ReadBlob(image,(size_t) quantum*image->columns,scanline);
495     if (count != 0)
496       {
497         /*
498           Allocate next image structure.
499         */
500         AcquireNextImage(image_info,image,exception);
501         if (GetNextImageInList(image) == (Image *) NULL)
502           {
503             status=MagickFalse;
504             break;
505           }
506         image=SyncNextImageInList(image);
507         status=SetImageProgress(image,LoadImagesTag,TellBlob(image),
508           GetBlobSize(image));
509         if (status == MagickFalse)
510           break;
511       }
512   } while (count != 0);
513   scanline=(unsigned char *) RelinquishMagickMemory(scanline);
514   (void) CloseBlob(image);
515   if (status == MagickFalse)
516     return(DestroyImageList(image));
517   return(GetFirstImageInList(image));
518 }
519 
520 /*
521 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
522 %                                                                             %
523 %                                                                             %
524 %                                                                             %
525 %   R e g i s t e r Y U V I m a g e                                           %
526 %                                                                             %
527 %                                                                             %
528 %                                                                             %
529 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
530 %
531 %  RegisterYUVImage() adds attributes for the YUV image format to
532 %  the list of supported formats.  The attributes include the image format
533 %  tag, a method to read and/or write the format, whether the format
534 %  supports the saving of more than one frame to the same file or blob,
535 %  whether the format supports native in-memory I/O, and a brief
536 %  description of the format.
537 %
538 %  The format of the RegisterYUVImage method is:
539 %
540 %      size_t RegisterYUVImage(void)
541 %
542 */
RegisterYUVImage(void)543 ModuleExport size_t RegisterYUVImage(void)
544 {
545   MagickInfo
546     *entry;
547 
548   entry=AcquireMagickInfo("YUV","YUV","CCIR 601 4:1:1 or 4:2:2");
549   entry->decoder=(DecodeImageHandler *) ReadYUVImage;
550   entry->encoder=(EncodeImageHandler *) WriteYUVImage;
551   entry->flags^=CoderAdjoinFlag;
552   entry->flags|=CoderRawSupportFlag;
553   (void) RegisterMagickInfo(entry);
554   return(MagickImageCoderSignature);
555 }
556 
557 /*
558 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559 %                                                                             %
560 %                                                                             %
561 %                                                                             %
562 %   U n r e g i s t e r Y U V I m a g e                                       %
563 %                                                                             %
564 %                                                                             %
565 %                                                                             %
566 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567 %
568 %  UnregisterYUVImage() removes format registrations made by the
569 %  YUV module from the list of supported formats.
570 %
571 %  The format of the UnregisterYUVImage method is:
572 %
573 %      UnregisterYUVImage(void)
574 %
575 */
UnregisterYUVImage(void)576 ModuleExport void UnregisterYUVImage(void)
577 {
578   (void) UnregisterMagickInfo("YUV");
579 }
580 
581 /*
582 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
583 %                                                                             %
584 %                                                                             %
585 %                                                                             %
586 %   W r i t e Y U V I m a g e                                                 %
587 %                                                                             %
588 %                                                                             %
589 %                                                                             %
590 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
591 %
592 %  WriteYUVImage() writes an image to a file in the digital YUV
593 %  (CCIR 601 4:1:1, plane or partition interlaced, or 4:2:2 plane, partition
594 %  interlaced or noninterlaced) bytes and returns it.
595 %
596 %  The format of the WriteYUVImage method is:
597 %
598 %      MagickBooleanType WriteYUVImage(const ImageInfo *image_info,
599 %        Image *image,ExceptionInfo *exception)
600 %
601 %  A description of each parameter follows.
602 %
603 %    o image_info: the image info.
604 %
605 %    o image:  The image.
606 %
607 %    o exception: return any errors or warnings in this structure.
608 %
609 */
WriteYUVImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)610 static MagickBooleanType WriteYUVImage(const ImageInfo *image_info,Image *image,
611   ExceptionInfo *exception)
612 {
613   Image
614     *chroma_image,
615     *yuv_image;
616 
617   InterlaceType
618     interlace;
619 
620   MagickBooleanType
621     status;
622 
623   MagickOffsetType
624     scene;
625 
626   const Quantum
627     *p,
628     *s;
629 
630   ssize_t
631     x;
632 
633   size_t
634     height,
635     imageListLength,
636     quantum,
637     width;
638 
639   ssize_t
640     horizontal_factor,
641     vertical_factor,
642     y;
643 
644   assert(image_info != (const ImageInfo *) NULL);
645   assert(image_info->signature == MagickCoreSignature);
646   assert(image != (Image *) NULL);
647   assert(image->signature == MagickCoreSignature);
648   if (image->debug != MagickFalse)
649     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
650   quantum=(size_t) (image->depth <= 8 ? 1 : 2);
651   interlace=image->interlace;
652   horizontal_factor=2;
653   vertical_factor=2;
654   if (image_info->sampling_factor != (char *) NULL)
655     {
656       GeometryInfo
657         geometry_info;
658 
659       MagickStatusType
660         flags;
661 
662       flags=ParseGeometry(image_info->sampling_factor,&geometry_info);
663       horizontal_factor=(ssize_t) geometry_info.rho;
664       vertical_factor=(ssize_t) geometry_info.sigma;
665       if ((flags & SigmaValue) == 0)
666         vertical_factor=horizontal_factor;
667       if ((horizontal_factor != 1) && (horizontal_factor != 2) &&
668           (vertical_factor != 1) && (vertical_factor != 2))
669         ThrowWriterException(CorruptImageError,"UnexpectedSamplingFactor");
670     }
671   if ((interlace == UndefinedInterlace) ||
672       ((interlace == NoInterlace) && (vertical_factor == 2)))
673     {
674       interlace=NoInterlace;    /* CCIR 4:2:2 */
675       if (vertical_factor == 2)
676         interlace=PlaneInterlace; /* CCIR 4:1:1 */
677     }
678   if (interlace != PartitionInterlace)
679     {
680       /*
681         Open output image file.
682       */
683       status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
684       if (status == MagickFalse)
685         return(status);
686     }
687   else
688     {
689       AppendImageFormat("Y",image->filename);
690       status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
691       if (status == MagickFalse)
692         return(status);
693     }
694   scene=0;
695   imageListLength=GetImageListLength(image);
696   do
697   {
698     /*
699       Sample image to an even width and height, if necessary.
700     */
701     image->depth=(size_t) (quantum == 1 ? 8 : 16);
702     width=image->columns+(image->columns & (horizontal_factor-1));
703     height=image->rows+(image->rows & (vertical_factor-1));
704     yuv_image=ResizeImage(image,width,height,TriangleFilter,exception);
705     if (yuv_image == (Image *) NULL)
706       {
707         (void) CloseBlob(image);
708         return(MagickFalse);
709       }
710     (void) TransformImageColorspace(yuv_image,YCbCrColorspace,exception);
711     /*
712       Downsample image.
713     */
714     chroma_image=ResizeImage(image,width/horizontal_factor,
715       height/vertical_factor,TriangleFilter,exception);
716     if (chroma_image == (Image *) NULL)
717       {
718         (void) CloseBlob(image);
719         return(MagickFalse);
720       }
721     (void) TransformImageColorspace(chroma_image,YCbCrColorspace,exception);
722     if (interlace == NoInterlace)
723       {
724         /*
725           Write noninterlaced YUV.
726         */
727         for (y=0; y < (ssize_t) yuv_image->rows; y++)
728         {
729           p=GetVirtualPixels(yuv_image,0,y,yuv_image->columns,1,exception);
730           if (p == (const Quantum *) NULL)
731             break;
732           s=GetVirtualPixels(chroma_image,0,y,chroma_image->columns,1,
733             exception);
734           if (s == (const Quantum *) NULL)
735             break;
736           for (x=0; x < (ssize_t) yuv_image->columns; x+=2)
737           {
738             if (quantum == 1)
739               {
740                 (void) WriteBlobByte(image,ScaleQuantumToChar(
741                   GetPixelGreen(yuv_image,s)));
742                 (void) WriteBlobByte(image,ScaleQuantumToChar(
743                   GetPixelRed(yuv_image,p)));
744                 p+=GetPixelChannels(yuv_image);
745                 (void) WriteBlobByte(image,ScaleQuantumToChar(
746                   GetPixelBlue(yuv_image,s)));
747                 (void) WriteBlobByte(image,ScaleQuantumToChar(
748                   GetPixelRed(yuv_image,p)));
749               }
750             else
751               {
752                 (void) WriteBlobByte(image,ScaleQuantumToChar(
753                   GetPixelGreen(yuv_image,s)));
754                 (void) WriteBlobShort(image,ScaleQuantumToShort(
755                   GetPixelRed(yuv_image,p)));
756                 p+=GetPixelChannels(yuv_image);
757                 (void) WriteBlobByte(image,ScaleQuantumToChar(
758                   GetPixelBlue(yuv_image,s)));
759                 (void) WriteBlobShort(image,ScaleQuantumToShort(
760                   GetPixelRed(yuv_image,p)));
761               }
762             p+=GetPixelChannels(yuv_image);
763             s++;
764           }
765           if (image->previous == (Image *) NULL)
766             {
767               status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
768                 image->rows);
769               if (status == MagickFalse)
770                 break;
771             }
772         }
773         yuv_image=DestroyImage(yuv_image);
774       }
775     else
776       {
777         /*
778           Initialize Y channel.
779         */
780         for (y=0; y < (ssize_t) yuv_image->rows; y++)
781         {
782           p=GetVirtualPixels(yuv_image,0,y,yuv_image->columns,1,exception);
783           if (p == (const Quantum *) NULL)
784             break;
785           for (x=0; x < (ssize_t) yuv_image->columns; x++)
786           {
787             if (quantum == 1)
788               (void) WriteBlobByte(image,ScaleQuantumToChar(
789                 GetPixelRed(yuv_image,p)));
790             else
791               (void) WriteBlobShort(image,ScaleQuantumToShort(
792                 GetPixelRed(yuv_image,p)));
793             p+=GetPixelChannels(yuv_image);
794           }
795           if (image->previous == (Image *) NULL)
796             {
797               status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
798                 image->rows);
799               if (status == MagickFalse)
800                 break;
801             }
802         }
803         yuv_image=DestroyImage(yuv_image);
804         if (image->previous == (Image *) NULL)
805           {
806             status=SetImageProgress(image,SaveImageTag,1,3);
807             if (status == MagickFalse)
808               break;
809           }
810         /*
811           Initialize U channel.
812         */
813         if (interlace == PartitionInterlace)
814           {
815             (void) CloseBlob(image);
816             AppendImageFormat("U",image->filename);
817             status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
818             if (status == MagickFalse)
819               return(status);
820           }
821         for (y=0; y < (ssize_t) chroma_image->rows; y++)
822         {
823           p=GetVirtualPixels(chroma_image,0,y,chroma_image->columns,1,
824             exception);
825           if (p == (const Quantum *) NULL)
826             break;
827           for (x=0; x < (ssize_t) chroma_image->columns; x++)
828           {
829             if (quantum == 1)
830               (void) WriteBlobByte(image,ScaleQuantumToChar(
831                 GetPixelGreen(chroma_image,p)));
832             else
833               (void) WriteBlobShort(image,ScaleQuantumToShort(
834                 GetPixelGreen(chroma_image,p)));
835             p+=GetPixelChannels(chroma_image);
836           }
837         }
838         if (image->previous == (Image *) NULL)
839           {
840             status=SetImageProgress(image,SaveImageTag,2,3);
841             if (status == MagickFalse)
842               break;
843           }
844         /*
845           Initialize V channel.
846         */
847         if (interlace == PartitionInterlace)
848           {
849             (void) CloseBlob(image);
850             AppendImageFormat("V",image->filename);
851             status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
852             if (status == MagickFalse)
853               return(status);
854           }
855         for (y=0; y < (ssize_t) chroma_image->rows; y++)
856         {
857           p=GetVirtualPixels(chroma_image,0,y,chroma_image->columns,1,
858             exception);
859           if (p == (const Quantum *) NULL)
860             break;
861           for (x=0; x < (ssize_t) chroma_image->columns; x++)
862           {
863             if (quantum == 1)
864               (void) WriteBlobByte(image,ScaleQuantumToChar(
865                 GetPixelBlue(chroma_image,p)));
866             else
867               (void) WriteBlobShort(image,ScaleQuantumToShort(
868                 GetPixelBlue(chroma_image,p)));
869             p+=GetPixelChannels(chroma_image);
870           }
871         }
872         if (image->previous == (Image *) NULL)
873           {
874             status=SetImageProgress(image,SaveImageTag,2,3);
875             if (status == MagickFalse)
876               break;
877           }
878       }
879     chroma_image=DestroyImage(chroma_image);
880     if (interlace == PartitionInterlace)
881       (void) CopyMagickString(image->filename,image_info->filename,
882         MagickPathExtent);
883     if (GetNextImageInList(image) == (Image *) NULL)
884       break;
885     image=SyncNextImageInList(image);
886     status=SetImageProgress(image,SaveImagesTag,scene++,imageListLength);
887     if (status == MagickFalse)
888       break;
889   } while (image_info->adjoin != MagickFalse);
890   (void) CloseBlob(image);
891   return(MagickTrue);
892 }
893