@@ -659,11 +659,152 @@ void avifRGBImageSetViewRect(avifRGBImage * dstImage, const avifRGBImage * srcIm
659659 dstImage -> format = srcImage -> format ;
660660 dstImage -> alphaPremultiplied = srcImage -> alphaPremultiplied ;
661661 dstImage -> isFloat = srcImage -> isFloat ;
662- const uint32_t channelCount = avifRGBFormatChannelCount (srcImage -> format );
663- const uint32_t bytesPerChannel = srcImage -> depth <= 8 ? 1 : 2 ;
664- const uint32_t bytesPerSample = srcImage -> format == AVIF_RGB_FORMAT_RGB_565 ? 2 : channelCount * bytesPerChannel ;
662+ const uint32_t bytesPerPixel = avifRGBImagePixelSize (srcImage );
665663 // This should not overflow if cropRect is a valid crop of the image.
666- const size_t offset = (size_t )cropRect -> y * srcImage -> rowBytes + (size_t )cropRect -> x * bytesPerSample ;
664+ const size_t offset = (size_t )cropRect -> y * srcImage -> rowBytes + (size_t )cropRect -> x * bytesPerPixel ;
667665 dstImage -> pixels = srcImage -> pixels + offset ;
668666 dstImage -> rowBytes = srcImage -> rowBytes ;
669667}
668+
669+ // NOTE: this saves the rotated pixels to a different image. Rotating an image in place is possible, but can be non trivial depending on the angle.
670+ // A 90° rotation can be implemented as a transposition operation followed by mirroring.
671+ // It's the transposition step that is non trivial for non-square images, see https://en.wikipedia.org/wiki/In-place_matrix_transposition
672+ avifResult avifRGBImageRotate (avifRGBImage * dstImage , const avifRGBImage * srcImage , const avifImageRotation * rotation )
673+ {
674+ const uint32_t bytesPerPixel = avifRGBImagePixelSize (srcImage );
675+ const uint8_t angle = rotation -> angle ;
676+ const uint32_t newWidth = (angle == 0 || angle == 2 ) ? srcImage -> width : srcImage -> height ;
677+ const uint32_t newHeight = (angle == 0 || angle == 2 ) ? srcImage -> height : srcImage -> width ;
678+ * dstImage = * srcImage ;
679+ dstImage -> width = newWidth ;
680+ dstImage -> height = newHeight ;
681+ dstImage -> pixels = NULL ;
682+ avifResult result = avifRGBImageAllocatePixels (dstImage );
683+ if (result != AVIF_RESULT_OK ) {
684+ return result ;
685+ }
686+
687+ if (rotation -> angle == 0 ) {
688+ const size_t bytesPerRow = (size_t )bytesPerPixel * srcImage -> width ;
689+ // 0 degrees. Just copy the rows as is.
690+ for (uint32_t j = 0 ; j < srcImage -> height ; ++ j ) {
691+ memcpy (dstImage -> pixels + ((size_t )j * dstImage -> rowBytes ), srcImage -> pixels + ((size_t )j * srcImage -> rowBytes ), bytesPerRow );
692+ }
693+ } else if (rotation -> angle == 1 ) {
694+ // 90 degrees anti-clockwise.
695+ for (uint32_t j = 0 ; j < srcImage -> height ; ++ j ) {
696+ for (uint32_t i = 0 ; i < srcImage -> width ; ++ i ) {
697+ // Source pixel at (i, j) goes to destination pixel at (j, srcImage->width - 1 - i).
698+ memcpy (dstImage -> pixels + ((size_t )(srcImage -> width - 1 - i ) * dstImage -> rowBytes ) + ((size_t )j * bytesPerPixel ),
699+ srcImage -> pixels + ((size_t )j * srcImage -> rowBytes ) + ((size_t )i * bytesPerPixel ),
700+ bytesPerPixel );
701+ }
702+ }
703+ } else if (rotation -> angle == 2 ) {
704+ // 180 degrees.
705+ for (uint32_t j = 0 ; j < srcImage -> height ; ++ j ) {
706+ for (uint32_t i = 0 ; i < srcImage -> width ; ++ i ) {
707+ // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, srcImage->height - 1 - j).
708+ memcpy (dstImage -> pixels + ((size_t )(srcImage -> height - 1 - j ) * dstImage -> rowBytes ) +
709+ ((size_t )(srcImage -> width - 1 - i ) * bytesPerPixel ),
710+ srcImage -> pixels + ((size_t )j * srcImage -> rowBytes ) + ((size_t )i * bytesPerPixel ),
711+ bytesPerPixel );
712+ }
713+ }
714+ } else if (rotation -> angle == 3 ) {
715+ // 90 degrees clockwise.
716+ for (uint32_t j = 0 ; j < srcImage -> height ; ++ j ) {
717+ for (uint32_t i = 0 ; i < srcImage -> width ; ++ i ) {
718+ // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, j).
719+ memcpy (dstImage -> pixels + ((size_t )i * dstImage -> rowBytes ) + ((size_t )(srcImage -> height - 1 - j ) * bytesPerPixel ),
720+ srcImage -> pixels + ((size_t )j * srcImage -> rowBytes ) + ((size_t )i * bytesPerPixel ),
721+ bytesPerPixel );
722+ }
723+ }
724+ } else {
725+ return AVIF_RESULT_INVALID_ARGUMENT ; // Invalid angle.
726+ }
727+ return AVIF_RESULT_OK ;
728+ }
729+
730+ avifResult avifRGBImageMirror (avifRGBImage * image , const avifImageMirror * mirror )
731+ {
732+ if (mirror -> axis == 0 ) { // Horizontal axis.
733+ const uint32_t bytesPerPixel = avifRGBImagePixelSize (image );
734+ // May be less than image->rowBytes e.g. if image is a cropped view.
735+ const size_t bytesPerRowToMove = (size_t )bytesPerPixel * image -> width ;
736+ // Top-to-bottom
737+ uint8_t * tempRow = (uint8_t * )avifAlloc (bytesPerRowToMove );
738+ if (!tempRow ) {
739+ return AVIF_RESULT_OUT_OF_MEMORY ;
740+ }
741+ for (uint32_t y = 0 ; y < image -> height / 2 ; ++ y ) {
742+ uint8_t * row1 = & image -> pixels [(size_t )y * image -> rowBytes ];
743+ uint8_t * row2 = & image -> pixels [(size_t )(image -> height - 1 - y ) * image -> rowBytes ];
744+ memcpy (tempRow , row1 , bytesPerRowToMove );
745+ memcpy (row1 , row2 , bytesPerRowToMove );
746+ memcpy (row2 , tempRow , bytesPerRowToMove );
747+ }
748+ avifFree (tempRow );
749+ } else if (mirror -> axis == 1 ) { // Vertical axis.
750+ const uint32_t bytesPerPixel = avifRGBImagePixelSize (image );
751+ uint8_t tempPixel [8 ]; // Max pixel size should be 8 bytes (RGBA 16-bit).
752+ if (bytesPerPixel > sizeof (tempPixel )) {
753+ return AVIF_RESULT_INVALID_ARGUMENT ;
754+ }
755+ for (uint32_t y = 0 ; y < image -> height ; ++ y ) {
756+ uint8_t * row = & image -> pixels [(size_t )y * image -> rowBytes ];
757+ for (uint32_t x = 0 ; x < image -> width / 2 ; ++ x ) {
758+ uint8_t * pixel1 = & row [(size_t )x * bytesPerPixel ];
759+ uint8_t * pixel2 = & row [(size_t )(image -> width - 1 - x ) * bytesPerPixel ];
760+ memcpy (tempPixel , pixel1 , bytesPerPixel );
761+ memcpy (pixel1 , pixel2 , bytesPerPixel );
762+ memcpy (pixel2 , tempPixel , bytesPerPixel );
763+ }
764+ }
765+ } else {
766+ return AVIF_RESULT_INVALID_ARGUMENT ; // Invalid axis value.
767+ }
768+
769+ return AVIF_RESULT_OK ;
770+ }
771+
772+ avifResult avifApplyTransforms (avifRGBImage * dstView , avifRGBImage * srcImage , const avifImage * avif )
773+ {
774+ // ISO/IEC 23000-22 (MIAF), Section 7.3.6.7:
775+ // These properties, if used, shall be indicated to be applied in the following order:
776+ // clean aperture first, then rotation, then mirror.
777+ * dstView = * srcImage ;
778+ if (avif -> transformFlags & AVIF_TRANSFORM_CLAP ) {
779+ avifCropRect cropRect ;
780+ avifDiagnostics diag ;
781+ if (avifCropRectFromCleanApertureBox (& cropRect , & avif -> clap , avif -> width , avif -> height , & diag ) &&
782+ (cropRect .x != 0 || cropRect .y != 0 || cropRect .width != avif -> width || cropRect .height != avif -> height )) {
783+ avifRGBImageSetViewRect (dstView , srcImage , & cropRect );
784+ } else {
785+ fprintf (stderr , "Invalid clean aperture box\n" );
786+ return AVIF_RESULT_INVALID_ARGUMENT ;
787+ }
788+ }
789+ if (avif -> transformFlags & AVIF_TRANSFORM_IROT && avif -> irot .angle != 0 ) {
790+ avifRGBImage tmpRgbImage ;
791+ avifResult result = avifRGBImageRotate (& tmpRgbImage , dstView , & avif -> irot );
792+ if (result != AVIF_RESULT_OK ) {
793+ fprintf (stderr , "Failed to apply rotation\n" );
794+ avifRGBImageFreePixels (& tmpRgbImage );
795+ return result ;
796+ }
797+ // We assume that srcImage owned its pixels and free them before replacing it with tmpRgbImage.
798+ avifRGBImageFreePixels (srcImage );
799+ * srcImage = tmpRgbImage ;
800+ * dstView = * srcImage ;
801+ }
802+ if (avif -> transformFlags & AVIF_TRANSFORM_IMIR ) {
803+ avifResult result = avifRGBImageMirror (dstView , & avif -> imir );
804+ if (result != AVIF_RESULT_OK ) {
805+ fprintf (stderr , "Failed to apply mirror\n" );
806+ return result ;
807+ }
808+ }
809+ return AVIF_RESULT_OK ;
810+ }
0 commit comments