Skip to content

Commit d01ba22

Browse files
authored
Apply rotation and mirroring when decoding to PNG or JPEG. (AOMediaCodec#3059)
AOMediaCodec#2427
1 parent 9688dcf commit d01ba22

7 files changed

Lines changed: 299 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The changes are relative to the previous release, unless the baseline is specifi
2626
* Support Sample Transform derived image items with grid input image items.
2727
* Add --sato flag to avifdec to enable Sample Transforms support at decoding.
2828
* Add --grid option to avifgainmaputil.
29-
* Apply clean aperture crop when decoding to PNG or JPEG.
29+
* Apply clean aperture crop, rotation and mirror when decoding to PNG or JPEG.
3030

3131
### Changed since 1.3.0
3232

apps/shared/avifjpeg.c

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,13 +1678,15 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int
16781678
goto cleanup;
16791679
}
16801680

1681-
avifRGBImage rgbView = rgbData;
1682-
if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
1683-
avifCropRect cropRect;
1684-
avifDiagnostics diag;
1685-
if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) &&
1686-
(cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) {
1687-
avifRGBImageSetViewRect(&rgbView, &rgbData, &cropRect);
1681+
// rgbView is a view on rgbData. avifApplyTransforms() may modify rgbData.
1682+
avifRGBImage rgbView;
1683+
avifResult transformResult = avifApplyTransforms(&rgbView, &rgbData, avif);
1684+
if (transformResult != AVIF_RESULT_OK) {
1685+
if (transformResult == AVIF_RESULT_INVALID_ARGUMENT) {
1686+
fprintf(stderr, "Warning, ignoring invalid transforms (clap/irot/imir)\n");
1687+
} else {
1688+
fprintf(stderr, "Failed to apply transforms: %s\n", avifResultToString(transformResult));
1689+
goto cleanup;
16881690
}
16891691
}
16901692

@@ -1724,15 +1726,14 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int
17241726
}
17251727
memcpy(exif.data, AVIF_JPEG_EXIF_HEADER, AVIF_JPEG_EXIF_HEADER_LENGTH);
17261728
memcpy(exif.data + AVIF_JPEG_EXIF_HEADER_LENGTH, avif->exif.data + exifTiffHeaderOffset, avif->exif.size - exifTiffHeaderOffset);
1727-
// Make sure the Exif orientation matches the irot/imir values.
1728-
// libheif does not have the same behavior. The orientation is applied to samples and orientation data is discarded there,
1729-
// see https://github.com/strukturag/libheif/blob/ea78603d8e47096606813d221725621306789ff2/examples/encoder_jpeg.cc#L187
1730-
const uint8_t orientation = avifImageGetExifOrientationFromIrotImir(avif);
1731-
result = avifSetExifOrientation(&exif, orientation);
1729+
// We already rotated the pixels if necessary in avifApplyTransforms(), so we set the orientation to 1 (no rotation, no mirror).
1730+
result = avifSetExifOrientation(&exif, 1);
17321731
if (result != AVIF_RESULT_OK) {
1733-
// Ignore errors if the orientation is the default one because not being able to set Exif orientation now
1734-
// means a reader will not be able to parse it later either.
1735-
if (orientation != 1) {
1732+
if (result == AVIF_RESULT_INVALID_EXIF_PAYLOAD || result == AVIF_RESULT_NOT_IMPLEMENTED) {
1733+
// Either the Exif is invalid, or it doesn't have an orientation field.
1734+
// If it's invalid, we can consider it as equivalent to not having an orientation.
1735+
// In both cases, we can ignore the error.
1736+
} else {
17361737
fprintf(stderr, "Error writing JPEG metadata: %s\n", avifResultToString(result));
17371738
avifRWDataFree(&exif);
17381739
goto cleanup;
@@ -1747,12 +1748,6 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int
17471748
}
17481749
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, remainingExif.data, (unsigned int)remainingExif.size);
17491750
avifRWDataFree(&exif);
1750-
} else if (avifImageGetExifOrientationFromIrotImir(avif) != 1) {
1751-
// There is no Exif yet, but we need to store the orientation.
1752-
// TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Add a valid Exif payload or rotate the samples.
1753-
fprintf(stderr,
1754-
"Warning: Orientation %u was ignored, the output image was NOT rotated or mirrored\n",
1755-
avifImageGetExifOrientationFromIrotImir(avif));
17561751
}
17571752

17581753
if (avif->xmp.data && (avif->xmp.size > 0)) {

apps/shared/avifpng.c

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -651,9 +651,9 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3
651651
rgbDepth = 8;
652652
}
653653

654-
volatile avifBool hasClap = avif->transformFlags & AVIF_TRANSFORM_CLAP;
654+
volatile avifBool hasTransforms = avif->transformFlags & (AVIF_TRANSFORM_CLAP | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR);
655655
volatile avifBool copyYPlane = (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) && !avif->alphaPlane && (avif->depth == 8) &&
656-
(rgbDepth == 8) && !hasClap;
656+
(rgbDepth == 8) && !hasTransforms;
657657

658658
volatile int colorType;
659659
if (copyYPlane) {
@@ -685,18 +685,15 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3
685685
}
686686
}
687687

688-
volatile uint32_t width = avif->width;
689-
volatile uint32_t height = avif->height;
690-
688+
// rgbView is a view on rgbData. avifApplyTransforms() may modify rgbData.
691689
avifRGBImage rgbView = rgbData;
692-
if (hasClap) {
693-
avifCropRect cropRect;
694-
avifDiagnostics diag;
695-
if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) &&
696-
(cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) {
697-
avifRGBImageSetViewRect(&rgbView, &rgbData, &cropRect);
698-
width = cropRect.width;
699-
height = cropRect.height;
690+
avifResult transformResult = avifApplyTransforms(&rgbView, &rgbData, avif);
691+
if (transformResult != AVIF_RESULT_OK) {
692+
if (transformResult == AVIF_RESULT_INVALID_ARGUMENT) {
693+
fprintf(stderr, "Warning, ignoring invalid transforms (clap/irot/imir)\n");
694+
} else {
695+
fprintf(stderr, "Failed to apply transforms: %s\n", avifResultToString(transformResult));
696+
goto cleanup;
700697
}
701698
}
702699

@@ -737,6 +734,9 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3
737734
png_set_compression_level(png, compressionLevel);
738735
}
739736

737+
volatile uint32_t width = copyYPlane ? avif->width : rgbView.width;
738+
volatile uint32_t height = copyYPlane ? avif->height : rgbView.height;
739+
740740
png_set_IHDR(png, info, width, height, rgbDepth, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
741741

742742
const avifBool hasIcc = avif->icc.data && (avif->icc.size > 0);
@@ -846,13 +846,6 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3
846846
row += rowBytes;
847847
}
848848

849-
if (avifImageGetExifOrientationFromIrotImir(avif) != 1) {
850-
// TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Rotate the samples.
851-
fprintf(stderr,
852-
"Warning: Orientation %u was ignored, the output image was NOT rotated or mirrored\n",
853-
avifImageGetExifOrientationFromIrotImir(avif));
854-
}
855-
856849
if (rgbDepth > 8) {
857850
png_set_swap(png);
858851
}

apps/shared/avifutil.c

Lines changed: 145 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

apps/shared/avifutil.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols,
6464
// using avifCropRectFromCleanApertureBox().
6565
void avifRGBImageSetViewRect(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifCropRect * cropRect);
6666

67+
// Rotates srcImage into dstImage. The two pointers must be different (does not rotate in place).
68+
// Allocates the pixels of dstImage which must be freed by the caller.
69+
avifResult avifRGBImageRotate(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifImageRotation * rotation);
70+
71+
// Mirrors the image in place.
72+
avifResult avifRGBImageMirror(avifRGBImage * image, const avifImageMirror * mirror);
73+
74+
// Applies clap, irot and imir transforms (in this order) specified in the avif image to srcImage.
75+
// The result is in dstView which does not own its pixels.
76+
// srcImage may also have its pixel data modified. Assumes that srcImage DOES own its pixels.
77+
// If an error is returned, only some of the transforms may have been applied.
78+
avifResult avifApplyTransforms(avifRGBImage * dstView, avifRGBImage * srcImage, const avifImage * avif);
79+
6780
// This structure holds any timing data coming from source (typically non-AVIF) inputs being fed
6881
// into avifenc. If either or both values are 0, the timing is "invalid" / sentinel and the values
6982
// should be ignored. This structure is used to override the timing defaults in avifenc when the

tests/gtest/avifmetadatatest.cc

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -265,26 +265,26 @@ TEST(MetadataTest, ExifOrientation) {
265265
EXPECT_EQ(decoded->irot.angle, 1u);
266266
EXPECT_EQ(decoded->imir.axis, 0u);
267267

268-
// Exif orientation is kept in JPEG export.
268+
// JPEG: Exif orientation should be applied when saving and removed from Exif.
269269
ImagePtr temp_image =
270270
WriteAndReadImage(*image, "paris_exif_orientation_5.jpg");
271271
ASSERT_NE(temp_image, nullptr);
272-
EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, temp_image->exif));
273-
EXPECT_EQ(image->transformFlags, temp_image->transformFlags);
274-
EXPECT_EQ(image->irot.angle, temp_image->irot.angle);
275-
EXPECT_EQ(image->imir.axis, temp_image->imir.axis);
276-
EXPECT_EQ(image->width, temp_image->width); // Samples are left untouched.
272+
EXPECT_FALSE(testutil::AreByteSequencesEqual(image->exif, temp_image->exif));
273+
EXPECT_EQ(temp_image->transformFlags, AVIF_TRANSFORM_NONE);
274+
// Samples have been rotated.
275+
EXPECT_EQ(image->width, temp_image->height);
276+
EXPECT_EQ(image->height, temp_image->width);
277277

278-
// Exif orientation in PNG export should be ignored or discarded.
278+
// PNG: Exif orientation should be applied when saving and removed from Exif.
279279
temp_image = WriteAndReadImage(*image, "paris_exif_orientation_5.png");
280280
ASSERT_NE(temp_image, nullptr);
281281
EXPECT_FALSE(testutil::AreByteSequencesEqual(image->exif, temp_image->exif));
282282
EXPECT_EQ(
283283
temp_image->transformFlags & (AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR),
284284
avifTransformFlags{0});
285-
// TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Fix orientation
286-
// not being applied to PNG samples.
287-
EXPECT_EQ(image->width, temp_image->width /* should be height here */);
285+
// Samples have been rotated.
286+
EXPECT_EQ(image->width, temp_image->height);
287+
EXPECT_EQ(image->height, temp_image->width);
288288
}
289289

290290
TEST(MetadataTest, AllExifOrientations) {
@@ -325,15 +325,16 @@ TEST(MetadataTest, ExifOrientationAndForcedImir) {
325325
EXPECT_EQ(decoded->irot.angle, 0u);
326326
EXPECT_EQ(decoded->imir.axis, image->imir.axis);
327327

328-
// Exif orientation is set equivalent to irot/imir in JPEG export.
329-
// Existing Exif orientation is overwritten.
328+
// Exif orientation is discarded (set to 1) and samples are rotated/mirrored
329+
// according to irot/imir in JPEG export.
330330
const ImagePtr temp_image =
331331
WriteAndReadImage(*image, "paris_exif_orientation_2.jpg");
332332
ASSERT_NE(temp_image, nullptr);
333333
EXPECT_FALSE(testutil::AreByteSequencesEqual(image->exif, temp_image->exif));
334-
EXPECT_EQ(image->transformFlags, temp_image->transformFlags);
335-
EXPECT_EQ(image->imir.axis, temp_image->imir.axis);
336-
EXPECT_EQ(image->width, temp_image->width); // Samples are left untouched.
334+
EXPECT_EQ(temp_image->transformFlags, AVIF_TRANSFORM_NONE);
335+
// Samples have been mirrored but you can't really tell from the dimensions.
336+
EXPECT_EQ(image->width, temp_image->width);
337+
EXPECT_EQ(image->height, temp_image->height);
337338
}
338339

339340
TEST(MetadataTest, RotatedJpegBecauseOfIrotImir) {
@@ -356,9 +357,9 @@ TEST(MetadataTest, RotatedJpegBecauseOfIrotImir) {
356357
EXPECT_EQ(
357358
temp_image->transformFlags & (AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR),
358359
avifTransformFlags{0});
359-
// TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Fix orientation
360-
// not being applied to JPEG samples.
361-
EXPECT_EQ(image->width, temp_image->width /* should be height here */);
360+
// Samples have been rotated.
361+
EXPECT_EQ(image->width, temp_image->height);
362+
EXPECT_EQ(image->height, temp_image->width);
362363
}
363364

364365
TEST(MetadataTest, ExifIfdOffsetLoopingTo8) {

0 commit comments

Comments
 (0)