From ba644f731b33c63852ff1aa57702e3022e093c42 Mon Sep 17 00:00:00 2001 From: KiYun Roe Date: Thu, 2 Oct 2025 18:08:11 -0700 Subject: [PATCH] Use UIGraphicsImageRenderer in GTMUIImage+Resize. (#541) This reverts commit d368b1733dc0060aae328671c33c084ba04a7477, which was for "Revert the changes to iPhone/GTMUIImage+Resize.m (#552)", but also includes the following additional changes to fix the reason for #552. Use source image scale in gtm_imageByResizingToSize. This matches the way scale is set in gtm_imageByRotating. Add tests to verify that image scale is preserved. --- iPhone/GTMUIImage+Resize.m | 61 +++++++++-------- iPhone/GTMUIImage+ResizeTest.m | 118 +++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 28 deletions(-) diff --git a/iPhone/GTMUIImage+Resize.m b/iPhone/GTMUIImage+Resize.m index 680e4130..d26abbdc 100644 --- a/iPhone/GTMUIImage+Resize.m +++ b/iPhone/GTMUIImage+Resize.m @@ -87,10 +87,14 @@ - (UIImage *)gtm_imageByResizingToSize:(CGSize)targetSize // Resize photo. Use UIImage drawing methods because they respect // UIImageOrientation as opposed to CGContextDrawImage(). - UIGraphicsBeginImageContext(targetSize); - [self drawInRect:projectTo]; - UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIGraphicsImageRendererFormat *rendererFormat = [[UIGraphicsImageRendererFormat alloc] init]; + rendererFormat.scale = self.scale; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:targetSize + format:rendererFormat]; + UIImage *resizedPhoto = + [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) { + [self drawInRect:projectTo]; + }]; return resizedPhoto; } @@ -156,30 +160,31 @@ - (UIImage *)gtm_imageByRotating:(UIImageOrientation)orientation { return nil; } - UIGraphicsBeginImageContextWithOptions(bounds.size, NO, self.scale); - CGContextRef context = UIGraphicsGetCurrentContext(); - - switch (orientation) { - case UIImageOrientationLeft: - case UIImageOrientationLeftMirrored: - case UIImageOrientationRight: - case UIImageOrientationRightMirrored: - CGContextScaleCTM(context, -1.0, 1.0); - CGContextTranslateCTM(context, -rect.size.height, 0.0); - break; - - default: - CGContextScaleCTM(context, 1.0, -1.0); - CGContextTranslateCTM(context, 0.0, -rect.size.height); - break; - } - - CGContextConcatCTM(context, transform); - CGContextDrawImage(context, rect, [self CGImage]); - - UIImage *rotatedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - + UIGraphicsImageRendererFormat *rendererFormat = [[UIGraphicsImageRendererFormat alloc] init]; + rendererFormat.scale = self.scale; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size + format:rendererFormat]; + UIImage *rotatedImage = + [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) { + CGContextRef context = rendererContext.CGContext; + switch (orientation) { + case UIImageOrientationLeft: + case UIImageOrientationLeftMirrored: + case UIImageOrientationRight: + case UIImageOrientationRightMirrored: + CGContextScaleCTM(context, -1.0, 1.0); + CGContextTranslateCTM(context, -rect.size.height, 0.0); + break; + + default: + CGContextScaleCTM(context, 1.0, -1.0); + CGContextTranslateCTM(context, 0.0, -rect.size.height); + break; + } + + CGContextConcatCTM(context, transform); + CGContextDrawImage(context, rect, [self CGImage]); + }]; return rotatedImage; } diff --git a/iPhone/GTMUIImage+ResizeTest.m b/iPhone/GTMUIImage+ResizeTest.m index c62618d9..0782be6e 100644 --- a/iPhone/GTMUIImage+ResizeTest.m +++ b/iPhone/GTMUIImage+ResizeTest.m @@ -68,6 +68,7 @@ - (void)testImageByResizingWithoutPreservingAspectRatio { // Resize with same aspect ratio. CGSize size50x50 = CGSizeMake(50, 50); + CGFloat expectedScale = [originalImage scale]; actual = [originalImage gtm_imageByResizingToSize:size50x50 preserveAspectRatio:NO trimToFit:NO]; @@ -75,6 +76,12 @@ - (void)testImageByResizingWithoutPreservingAspectRatio { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size50x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Resize with different aspect ratio CGSize size60x40 = CGSizeMake(60, 40); @@ -85,6 +92,12 @@ - (void)testImageByResizingWithoutPreservingAspectRatio { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size60x40), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); CGSize size40x60 = CGSizeMake(40, 60); actual = [originalImage gtm_imageByResizingToSize:size40x60 @@ -94,6 +107,12 @@ - (void)testImageByResizingWithoutPreservingAspectRatio { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size40x60), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); } - (void)testImageByResizingPreservingAspectRatioWithoutClip { @@ -104,6 +123,7 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { // Landscape resize to 50x50, but clipped to 50x25. CGSize size50x50 = CGSizeMake(50, 50); CGSize expected50x25 = CGSizeMake(50, 25); + CGFloat expectedScale = [landscapeImage scale]; actual = [landscapeImage gtm_imageByResizingToSize:size50x50 preserveAspectRatio:YES trimToFit:NO]; @@ -111,6 +131,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected50x25), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Landscape resize to 60x40, but clipped to 60x30. CGSize size60x40 = CGSizeMake(60, 40); @@ -123,6 +149,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected60x30), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Landscape resize to 40x60, but clipped to 40x20. CGSize expected40x20 = CGSizeMake(40, 20); @@ -134,6 +166,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected40x20), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait Image UIImage *portraitImage = [self testImageNamed:@"GTMUIImage+Resize_50x100"]; @@ -147,6 +185,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected25x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait resize to 60x40, but clipped to 20x40. CGSize expected20x40 = CGSizeMake(20, 40); @@ -157,6 +201,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected20x40), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait resize to 40x60, but clipped to 30x60. CGSize expected30x60 = CGSizeMake(30, 60); @@ -167,6 +217,12 @@ - (void)testImageByResizingPreservingAspectRatioWithoutClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(expected30x60), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); } - (void)testImageByResizingPreservingAspectRatioWithClip { @@ -176,6 +232,7 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { // Landscape resize to 50x50 CGSize size50x50 = CGSizeMake(50, 50); + CGFloat expectedScale = [landscapeImage scale]; actual = [landscapeImage gtm_imageByResizingToSize:size50x50 preserveAspectRatio:YES trimToFit:YES]; @@ -183,6 +240,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size50x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Landscape resize to 60x40 CGSize size60x40 = CGSizeMake(60, 40); @@ -193,6 +256,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size60x40), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Landscape resize to 40x60 CGSize size40x60 = CGSizeMake(40, 60); @@ -203,6 +272,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size40x60), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait Image. UIImage *portraitImage = [self testImageNamed:@"GTMUIImage+Resize_50x100"]; @@ -215,6 +290,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size50x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait resize to 60x40 actual = [portraitImage gtm_imageByResizingToSize:size60x40 @@ -224,6 +305,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size60x40), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Portrait resize to 40x60. actual = [portraitImage gtm_imageByResizingToSize:size40x60 @@ -233,6 +320,12 @@ - (void)testImageByResizingPreservingAspectRatioWithClip { @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size40x60), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); } - (void)testImageByRotating { @@ -241,6 +334,7 @@ - (void)testImageByRotating { CGSize size50x100 = CGSizeMake(50, 100); CGSize size100x50 = CGSizeMake(100, 50); + CGFloat expectedScale = [landscapeImage scale]; // Rotate 90 degrees. UIImage *actual = [landscapeImage gtm_imageByRotating:UIImageOrientationRight]; @@ -248,6 +342,12 @@ - (void)testImageByRotating { XCTAssertTrue(CGSizeEqualToSize([actual size], size50x100), @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size50x100), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Rotate 180 degrees. actual = [landscapeImage gtm_imageByRotating:UIImageOrientationDown]; @@ -255,6 +355,12 @@ - (void)testImageByRotating { XCTAssertTrue(CGSizeEqualToSize([actual size], size100x50), @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size100x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Rotate 270 degrees. actual = [landscapeImage gtm_imageByRotating:UIImageOrientationLeft]; @@ -262,6 +368,12 @@ - (void)testImageByRotating { XCTAssertTrue(CGSizeEqualToSize([actual size], size50x100), @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size50x100), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); // Rotate 360 degrees. actual = [landscapeImage gtm_imageByRotating:UIImageOrientationUp]; @@ -269,6 +381,12 @@ - (void)testImageByRotating { XCTAssertTrue(CGSizeEqualToSize([actual size], size100x50), @"Resized image should equal size: %@ actual: %@", NSStringFromCGSize(size100x50), NSStringFromCGSize([actual size])); + XCTAssertEqualWithAccuracy([actual scale], + expectedScale, + 0.000000001, + @"Resized image should have scale: %f actual: %f", + expectedScale, + [actual scale]); } @end