| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- * (c) Matt Galloway
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDTestCase.h"
- #import "UIColor+SDHexString.h"
- @interface SDWebImageDecoderTests : SDTestCase
- @end
- @implementation SDWebImageDecoderTests
- - (void)test01ThatDecodedImageWithNilImageReturnsNil {
- expect([UIImage sd_decodedImageWithImage:nil]).to.beNil();
- expect([UIImage sd_decodedAndScaledDownImageWithImage:nil]).to.beNil();
- }
- - (void)test02ThatDecodedImageWithImageWorksWithARegularJPGImage {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- expect(decodedImage.size.width).to.equal(image.size.width);
- expect(decodedImage.size.height).to.equal(image.size.height);
- }
- - (void)test03ThatDecodedImageWithImageDoesNotDecodeAnimatedImages {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- #if SD_MAC
- UIImage *animatedImage = image;
- #else
- UIImage *animatedImage = [UIImage animatedImageWithImages:@[image] duration:0];
- #endif
- UIImage *decodedImage = [UIImage sd_decodedImageWithImage:animatedImage];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).to.equal(animatedImage);
- }
- - (void)test04ThatDecodedImageWithImageWorksWithAlphaImages {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- }
- - (void)test05ThatDecodedImageWithImageWorksEvenWithMonochromeImage {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"MonochromeTestImage" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- expect(decodedImage.size.width).to.equal(image.size.width);
- expect(decodedImage.size.height).to.equal(image.size.height);
- }
- - (void)test06ThatDecodeAndScaleDownImageWorks {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:(60 * 1024 * 1024)];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- expect(decodedImage.size.width).toNot.equal(image.size.width);
- expect(decodedImage.size.height).toNot.equal(image.size.height);
- expect(decodedImage.size.width * decodedImage.size.height).to.beLessThanOrEqualTo(60 * 1024 * 1024 / 4); // how many pixels in 60 megs
- }
- - (void)test07ThatDecodeAndScaleDownImageDoesNotScaleSmallerImage {
- // check when user use the larget bytes than image pixels byets, we do not scale up the image (defaults 60MB means 3965x3965 pixels)
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- expect(decodedImage.size.width).to.equal(image.size.width);
- expect(decodedImage.size.height).to.equal(image.size.height);
- }
- - (void)test07ThatDecodeAndScaleDownImageScaleSmallerBytes {
- // Check when user provide too small bytes, we scale it down to 1x1, but not return the force decoded original size image
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:1];
- expect(decodedImage).toNot.beNil();
- expect(decodedImage).toNot.equal(image);
- expect(decodedImage.size.width).to.equal(1);
- expect(decodedImage.size.height).to.equal(1);
- }
- -(void)test07ThatDecodeAndScaleDownAlwaysCompleteRendering {
- // Check that when the height of the image used is not evenly divisible by the height of the tile, the output image can also be rendered completely.
-
- // Check that when the height of the image used will led to loss of precision. the output image can also be rendered completely,
-
- UIColor *imageColor = UIColor.blackColor;
- CGSize imageSize = CGSizeMake(1029, 1029);
- CGRect imageRect = CGRectMake(0, 0, imageSize.width, imageSize.height);
- SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
- format.scale = 1;
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:imageSize format:format];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- CGContextSetFillColorWithColor(context, [imageColor CGColor]);
- CGContextFillRect(context, imageRect);
- }];
-
- UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:1 * 1024 * 1024];
- UIColor *testColor1 = [decodedImage sd_colorAtPoint:CGPointMake(0, decodedImage.size.height - 1)];
- UIColor *testColor2 = [decodedImage sd_colorAtPoint:CGPointMake(0, decodedImage.size.height - 9)];
- expect(testColor1.sd_hexString).equal(imageColor.sd_hexString);
- expect(testColor2.sd_hexString).equal(imageColor.sd_hexString);
- }
- - (void)test08ThatEncodeAlphaImageToJPGWithBackgroundColor {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIColor *backgroundColor = [UIColor blackColor];
- NSData *encodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeBackgroundColor : backgroundColor}];
- expect(encodedData).notTo.beNil();
- UIImage *decodedImage = [SDImageCodersManager.sharedManager decodedImageWithData:encodedData options:nil];
- expect(decodedImage).notTo.beNil();
- expect(decodedImage.size.width).to.equal(image.size.width);
- expect(decodedImage.size.height).to.equal(image.size.height);
- // Check background color, should not be white but the black color
- UIColor *testColor = [decodedImage sd_colorAtPoint:CGPointMake(1, 1)];
- expect(testColor.sd_hexString).equal(backgroundColor.sd_hexString);
- }
- - (void)test09ThatJPGImageEncodeWithMaxFileSize {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
- UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- // This large JPEG encoding size between (770KB ~ 2.23MB)
- NSUInteger limitFileSize = 1 * 1024 * 1024; // 1MB
- // 100 quality (biggest)
- NSData *maxEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:nil];
- expect(maxEncodedData).notTo.beNil();
- expect(maxEncodedData.length).beGreaterThan(limitFileSize);
- // 0 quality (smallest)
- NSData *minEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeCompressionQuality : @(0.01)}]; // Seems 0 has some bugs in old macOS
- expect(minEncodedData).notTo.beNil();
- expect(minEncodedData.length).beLessThan(limitFileSize);
- NSData *limitEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeMaxFileSize : @(limitFileSize)}];
- expect(limitEncodedData).notTo.beNil();
- // So, if we limit the file size, the output data should in (770KB ~ 2.23MB)
- expect(limitEncodedData.length).beLessThan(maxEncodedData.length);
- expect(limitEncodedData.length).beGreaterThan(minEncodedData.length);
- }
- - (void)test10ThatAnimatedImageCacheImmediatelyWorks {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"png"];
- NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
-
- // Check that animated image rendering should not use lazy decoding (performance related)
- CFAbsoluteTime begin = CFAbsoluteTimeGetCurrent();
- SDImageAPNGCoder *coder = [[SDImageAPNGCoder alloc] initWithAnimatedImageData:testImageData options:@{SDImageCoderDecodeFirstFrameOnly : @(NO)}];
- UIImage *imageWithoutLazyDecoding = [coder animatedImageFrameAtIndex:0];
- CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
- CFAbsoluteTime duration = end - begin;
- expect(imageWithoutLazyDecoding.sd_isDecoded).beTruthy();
-
- // Check that static image rendering should use lazy decoding
- CFAbsoluteTime begin2 = CFAbsoluteTimeGetCurrent();
- SDImageAPNGCoder *coder2 = SDImageAPNGCoder.sharedCoder;
- UIImage *imageWithLazyDecoding = [coder2 decodedImageWithData:testImageData options:@{SDImageCoderDecodeFirstFrameOnly : @(YES)}];
- CFAbsoluteTime end2 = CFAbsoluteTimeGetCurrent();
- CFAbsoluteTime duration2 = end2 - begin2;
- expect(imageWithLazyDecoding.sd_isDecoded).beFalsy();
-
- // lazy decoding need less time (10x)
- expect(duration2 * 10.0).beLessThan(duration);
- }
- - (void)test11ThatAPNGPCoderWorks {
- NSURL *APNGURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"apng"];
- [self verifyCoder:[SDImageAPNGCoder sharedCoder]
- withLocalImageURL:APNGURL
- supportsEncoding:YES
- isAnimatedImage:YES];
- }
- - (void)test12ThatGIFCoderWorks {
- NSURL *gifURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"gif"];
- [self verifyCoder:[SDImageGIFCoder sharedCoder]
- withLocalImageURL:gifURL
- supportsEncoding:YES
- isAnimatedImage:YES];
- }
- - (void)test12ThatGIFWithoutLoopCountPlayOnce {
- // When GIF metadata does not contains any loop count information (`kCGImagePropertyGIFLoopCount`'s value nil)
- // The standard says it should just play once. See: http://www6.uniovi.es/gifanim/gifabout.htm
- // This behavior is different from other modern animated image format like APNG/WebP. Which will play infinitely
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestLoopCount" ofType:@"gif"];
- NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
- UIImage *image = [SDImageGIFCoder.sharedCoder decodedImageWithData:testImageData options:nil];
- expect(image.sd_imageLoopCount).equal(1);
- }
- - (void)test13ThatHEICWorks {
- if (@available(iOS 11, tvOS 11, macOS 10.13, *)) {
- NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"heic"];
- #if SD_MAC
- BOOL supportsEncoding = !SDTestCase.isCI; // GitHub Action Mac env currently does not support HEIC encoding
- #else
- BOOL supportsEncoding = YES; // GitHub Action Mac env with simulator, supported from 20240707.1
- #endif
- [self verifyCoder:[SDImageIOCoder sharedCoder]
- withLocalImageURL:heicURL
- supportsEncoding:supportsEncoding
- isAnimatedImage:NO];
- }
- }
- - (void)test14ThatHEIFWorks {
- if (@available(iOS 11, tvOS 11, macOS 10.13, *)) {
- NSURL *heifURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"heif"];
- BOOL supportsEncoding = NO; // public.heif UTI alwsays return false, use public.heic
- [self verifyCoder:[SDImageIOCoder sharedCoder]
- withLocalImageURL:heifURL
- supportsEncoding:supportsEncoding
- isAnimatedImage:NO];
- }
- }
- - (void)test15ThatCodersManagerWorks {
- SDImageCodersManager *manager = [[SDImageCodersManager alloc] init];
- manager.coders = @[SDImageIOCoder.sharedCoder];
- expect([manager canDecodeFromData:nil]).beTruthy(); // Image/IO will return YES for future format
- expect([manager decodedImageWithData:nil options:nil]).beNil();
- expect([manager canEncodeToFormat:SDImageFormatUndefined]).beTruthy(); // Image/IO will return YES for future format
- expect([manager encodedDataWithImage:nil format:SDImageFormatUndefined options:nil]).beNil();
- }
- - (void)test16ThatHEICAnimatedWorks {
- if (@available(iOS 13, tvOS 13, macOS 10.15, *)) {
- NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"heics"];
- BOOL supportsEncoding = !SDTestCase.isCI; // GitHub Action Mac env currently does not support HEICS animated encoding (but HEIC supported, I don't know why)
- // See: #3227
- BOOL isAnimatedImage = YES;
- [self verifyCoder:[SDImageHEICCoder sharedCoder]
- withLocalImageURL:heicURL
- supportsEncoding:supportsEncoding
- encodingFormat:SDImageFormatHEIC
- isAnimatedImage:isAnimatedImage
- isVectorImage:NO];
- }
- }
- - (void)test17ThatPDFWorks {
- NSURL *pdfURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"pdf"];
- [self verifyCoder:[SDImageIOCoder sharedCoder]
- withLocalImageURL:pdfURL
- supportsEncoding:NO
- encodingFormat:SDImageFormatUndefined
- isAnimatedImage:NO
- isVectorImage:YES];
- }
- #if !SD_TV
- - (void)test18ThatStaticWebPWorks {
- if (@available(iOS 14, tvOS 14, macOS 11, *)) {
- NSURL *staticWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageStatic" withExtension:@"webp"];
- [self verifyCoder:[SDImageAWebPCoder sharedCoder]
- withLocalImageURL:staticWebPURL
- supportsEncoding:NO // Currently (iOS 14.0) seems no encoding support
- encodingFormat:SDImageFormatWebP
- isAnimatedImage:NO
- isVectorImage:NO];
- }
- }
- #endif
- #if !SD_TV
- - (void)test19ThatAnimatedWebPWorks {
- if (@available(iOS 14, tvOS 14, macOS 11, *)) {
- NSURL *staticWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"webp"];
- [self verifyCoder:[SDImageAWebPCoder sharedCoder]
- withLocalImageURL:staticWebPURL
- supportsEncoding:NO // Currently (iOS 14.0) seems no encoding support
- encodingFormat:SDImageFormatWebP
- isAnimatedImage:YES
- isVectorImage:NO];
- }
- }
- #endif
- - (void)test20ThatImageIOAnimatedCoderAbstractClass {
- SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init];
- @try {
- [coder canEncodeToFormat:SDImageFormatPNG];
- XCTFail("Should throw exception");
- } @catch (NSException *exception) {
- expect(exception);
- }
- }
- - (void)test21ThatEmbedThumbnailHEICWorks {
- #if SD_MAC
- BOOL supportsEncoding = !SDTestCase.isCI; // GitHub Action Mac env currently does not support HEIC encoding
- #else
- BOOL supportsEncoding = YES; // GitHub Action Mac env with simulator, supported from 20240707.1
- #endif
- if (!supportsEncoding) {
- return;
- }
- if (@available(iOS 11, tvOS 11, macOS 10.13, *)) {
- // The input HEIC does not contains any embed thumbnail
- NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"heic"];
- CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)heicURL, nil);
- expect(source).notTo.beNil();
- NSArray *thumbnailImages = [self thumbnailImagesFromImageSource:source];
- expect(thumbnailImages.count).equal(0);
-
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil);
- #if SD_UIKIT
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation: UIImageOrientationUp];
- #else
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation:kCGImagePropertyOrientationUp];
- #endif
- CGImageRelease(imageRef);
- // Encode with embed thumbnail
- NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatHEIC options:@{SDImageCoderEncodeEmbedThumbnail : @(YES)}];
-
- // The new HEIC contains one embed thumbnail
- CGImageSourceRef source2 = CGImageSourceCreateWithData((__bridge CFDataRef)encodedData, nil);
- expect(source2).notTo.beNil();
- NSArray *thumbnailImages2 = [self thumbnailImagesFromImageSource:source2];
- expect(thumbnailImages2.count).equal(1);
-
- // Currently ImageIO has no control to custom embed thumbnail pixel size, just check the behavior :)
- NSDictionary *thumbnailImageInfo = thumbnailImages2.firstObject;
- NSUInteger thumbnailWidth = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyWidth] unsignedIntegerValue];
- NSUInteger thumbnailHeight = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyHeight] unsignedIntegerValue];
- expect(thumbnailWidth).equal(320);
- expect(thumbnailHeight).equal(212);
- }
- }
- - (void)test22ThatThumbnailDecodeCalculation {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
- NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
- CGSize thumbnailSize = CGSizeMake(400, 300);
- UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:testImageData options:@{
- SDImageCoderDecodePreserveAspectRatio: @(YES),
- SDImageCoderDecodeThumbnailPixelSize: @(thumbnailSize)}];
- CGSize imageSize = image.size;
- expect(imageSize.width).equal(400);
- expect(imageSize.height).equal(263);
- // `CGImageSourceCreateThumbnailAtIndex` should always produce non-lazy CGImage
- CGImageRef cgImage = image.CGImage;
- expect([SDImageCoderHelper CGImageIsLazy:cgImage]).beFalsy();
- expect(image.sd_isDecoded).beTruthy();
- }
- - (void)test23ThatThumbnailEncodeCalculation {
- NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
- NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
- UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:testImageData options:nil];
- expect(image.size).equal(CGSizeMake(5250, 3450));
- // `CGImageSourceCreateImageAtIndex` should always produce lazy CGImage
- CGImageRef cgImage = image.CGImage;
- expect([SDImageCoderHelper CGImageIsLazy:cgImage]).beTruthy();
- expect(image.sd_isDecoded).beFalsy();
- CGSize thumbnailSize = CGSizeMake(4000, 4000); // 3450 < 4000 < 5250
- NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatJPEG options:@{
- SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
- }];
- UIImage *encodedImage = [UIImage sd_imageWithData:encodedData];
- expect(encodedImage.size).equal(CGSizeMake(4000, 2629));
- }
- - (void)test24ThatScaleSizeCalculation {
- // preserveAspectRatio true
- CGSize size1 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(150, 150) preserveAspectRatio:YES shouldScaleUp:NO];
- expect(size1).equal(CGSizeMake(75, 150));
- CGSize size2 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(150, 150) preserveAspectRatio:YES shouldScaleUp:YES];
- expect(size2).equal(CGSizeMake(75, 150));
- CGSize size3 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(300, 300) preserveAspectRatio:YES shouldScaleUp:NO];
- expect(size3).equal(CGSizeMake(100, 200));
- CGSize size4 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(300, 300) preserveAspectRatio:YES shouldScaleUp:YES];
- expect(size4).equal(CGSizeMake(150, 300));
-
- // preserveAspectRatio false
- CGSize size5 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(150, 150) preserveAspectRatio:NO shouldScaleUp:NO];
- expect(size5).equal(CGSizeMake(100, 150));
- CGSize size6 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(100, 200) scaleSize:CGSizeMake(150, 150) preserveAspectRatio:NO shouldScaleUp:YES];
- expect(size6).equal(CGSizeMake(150, 150));
-
- // 0 value
- CGSize size7 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(0, 0) scaleSize:CGSizeMake(999, 999) preserveAspectRatio:NO shouldScaleUp:NO];
- expect(size7).equal(CGSizeMake(0, 0));
- CGSize size8 = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(999, 999) scaleSize:CGSizeMake(0, 0) preserveAspectRatio:NO shouldScaleUp:NO];
- expect(size8).equal(CGSizeMake(999, 999));
- }
- - (void)test25ThatBMPWorks {
- NSURL *bmpURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"bmp"];
- [self verifyCoder:[SDImageIOCoder sharedCoder]
- withLocalImageURL:bmpURL
- supportsEncoding:YES
- encodingFormat:SDImageFormatBMP
- isAnimatedImage:NO
- isVectorImage:NO];
- }
- - (void)test26ThatRawImageTypeHintWorks {
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"nef"];
- NSData *data = [NSData dataWithContentsOfURL:url];
-
- // 1. Test without hint will use TIFF's IFD#0, which size should always be 160x120, see: http://lclevy.free.fr/nef/
- UIImage *image1 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil];
- expect(image1.size).equal(CGSizeMake(160, 120));
- expect(image1.sd_imageFormat).equal(SDImageFormatTIFF);
-
- #if SD_MAC || SD_IOS
- // 2. Test with NEF file extension should be NEF
- UIImage *image2 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeFileExtensionHint : @"nef"}];
- expect(image2.size).equal(CGSizeMake(3008, 2000));
- expect(image2.sd_imageFormat).equal(SDImageFormatRAW);
-
- // 3. Test with UTType hint should be NEF
- UIImage *image3 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeTypeIdentifierHint : @"com.nikon.raw-image"}];
- expect(image3.size).equal(CGSizeMake(3008, 2000));
- expect(image3.sd_imageFormat).equal(SDImageFormatRAW);
- #endif
- }
- - (void)test27ThatEncodeWithFramesWorks {
- // Mock
- NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
- NSUInteger frameCount = 5;
- for (size_t i = 0; i < frameCount; i++) {
- CGSize size = CGSizeMake(100, 100);
- SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size];
- UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
- CGContextSetRGBFillColor(context, 1.0 / i, 0.0, 0.0, 1.0);
- CGContextSetRGBStrokeColor(context, 1.0 / i, 0.0, 0.0, 1.0);
- CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
- }];
- SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:0.1];
- [frames addObject:frame];
- }
-
- // Test old API
- UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
- NSData *data = [SDImageGIFCoder.sharedCoder encodedDataWithImage:animatedImage format:SDImageFormatGIF options:nil];
- expect(data).notTo.beNil();
- #if SD_MAC
- // Test implementation use SDAnimatedImageRep
- SDAnimatedImageRep *rep = (SDAnimatedImageRep *)animatedImage.representations.firstObject;
- expect([rep isKindOfClass:SDAnimatedImageRep.class]);
- expect(rep.animatedImageData).equal(data);
- expect(rep.animatedImageFormat).equal(SDImageFormatGIF);
- #endif
-
- // Test new API
- NSData *data2 = [SDImageGIFCoder.sharedCoder encodedDataWithFrames:frames loopCount:0 format:SDImageFormatGIF options:nil];
- expect(data2).notTo.beNil();
- }
- - (void)test28ThatNotTriggerCACopyImage {
- // 10 * 8 pixels, RGBA8888
- size_t width = 10;
- size_t height = 8;
- size_t bitsPerComponent = 8;
- size_t components = 4;
- size_t bitsPerPixel = bitsPerComponent * components;
- size_t bytesPerRow = SDByteAlign(bitsPerPixel / 8 * width, [SDImageCoderHelper preferredPixelFormat:YES].alignment);
- size_t size = bytesPerRow * height;
- uint8_t bitmap[size];
- for (size_t i = 0; i < size; i++) {
- bitmap[i] = 255;
- }
- CGColorSpaceRef colorspace = [SDImageCoderHelper colorSpaceGetDeviceRGB];
- CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:YES].bitmapInfo;
- CFDataRef data = CFDataCreate(NULL, bitmap, size);
- CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
- CFRelease(data);
- BOOL shouldInterpolate = YES;
- CGColorRenderingIntent intent = kCGRenderingIntentDefault;
- CGImageRef cgImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorspace, bitmapInfo, provider, NULL, shouldInterpolate, intent);
- CGDataProviderRelease(provider);
- XCTAssert(cgImage);
- BOOL result = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
- // Since it's 32 bytes aligned, return true
- XCTAssertTrue(result);
- // Let's force-decode to check again
- #if SD_MAC
- UIImage *image = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:kCGImagePropertyOrientationUp];
- #else
- UIImage *image = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:UIImageOrientationUp];
- #endif
- CGImageRelease(cgImage);
- UIImage *newImage = [SDImageCoderHelper decodedImageWithImage:image policy:SDImageForceDecodePolicyAutomatic];
- // Check policy works, since it's supported by CA hardware, which return the input image object, using pointer compare
- XCTAssertTrue(image == newImage);
- BOOL newResult = [SDImageCoderHelper CGImageIsHardwareSupported:newImage.CGImage];
- XCTAssertTrue(newResult);
- }
- - (void)test28ThatDoTriggerCACopyImage {
- // 10 * 8 pixels, RGBA8888
- size_t width = 10;
- size_t height = 8;
- size_t bitsPerComponent = 8;
- size_t components = 4;
- size_t bitsPerPixel = bitsPerComponent * components;
- size_t bytesPerRow = bitsPerPixel / 8 * width;
- size_t size = bytesPerRow * height;
- uint8_t bitmap[size];
- for (size_t i = 0; i < size; i++) {
- bitmap[i] = 255;
- }
- CGColorSpaceRef colorspace = [SDImageCoderHelper colorSpaceGetDeviceRGB];
- CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:YES].bitmapInfo;
- CFDataRef data = CFDataCreate(NULL, bitmap, size);
- CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
- CFRelease(data);
- BOOL shouldInterpolate = YES;
- CGColorRenderingIntent intent = kCGRenderingIntentDefault;
- CGImageRef cgImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorspace, bitmapInfo, provider, NULL, shouldInterpolate, intent);
- CGDataProviderRelease(provider);
- XCTAssert(cgImage);
- BOOL result = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
- // Since it's not 32 bytes aligned, return false
- XCTAssertFalse(result);
- // Let's force-decode to check again
- #if SD_MAC
- UIImage *image = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:kCGImagePropertyOrientationUp];
- #else
- UIImage *image = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:UIImageOrientationUp];
- #endif
- CGImageRelease(cgImage);
- UIImage *newImage = [SDImageCoderHelper decodedImageWithImage:image policy:SDImageForceDecodePolicyAutomatic];
- // Check policy works, since it's not supported by CA hardware, which return the different image object
- XCTAssertFalse(image == newImage);
- BOOL newResult = [SDImageCoderHelper CGImageIsHardwareSupported:newImage.CGImage];
- XCTAssertTrue(newResult);
- }
- - (void)test29ThatJFIFDecodeOrientationShouldNotApplyTwice {
- // I don't think this is SDWebImage's issue, it's Apple's ImgeIO Bug, but user complain about this: #3594
- // In W3C standard, JFIF should always be orientation up, and should not contains EXIF orientation
- // But some bad image editing tool will generate this kind of image :(
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestJFIF" withExtension:@"jpg"];
- NSData *data = [NSData dataWithContentsOfURL:url];
-
- UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil];
- expect(image.sd_imageFormat).equal(SDImageFormatJPEG);
- #if SD_UIKIT
- UIImageOrientation orientation = image.imageOrientation;
- expect(orientation).equal(UIImageOrientationDown);
- #endif
-
- UIImage *systemImage = [[UIImage alloc] initWithData:data];
- #if SD_UIKIT
- orientation = systemImage.imageOrientation;
- if (@available(iOS 18.0, tvOS 18.0, watchOS 11.0, *)) {
- // Apple fix/hack this kind of JFIF on iOS 18
- expect(orientation).equal(UIImageOrientationUp);
- } else {
- expect(orientation).equal(UIImageOrientationDown);
- }
- #endif
-
- // Check bitmap color equal, between our usage of ImageIO decoder and Apple system API behavior
- // So, this means, if Apple has bugs, we have bugs too, it's not our fault :)
- UIColor *testColor1 = [image sd_colorAtPoint:CGPointMake(1, 1)];
- UIColor *testColor2 = [systemImage sd_colorAtPoint:CGPointMake(1, 1)];
- CGFloat r1, g1, b1, a1;
- CGFloat r2, g2, b2, a2;
- [testColor1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
- [testColor2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
- expect(r1).beCloseToWithin(r2, 0.01);
- expect(g1).beCloseToWithin(g2, 0.01);
- expect(b1).beCloseToWithin(b2, 0.01);
- expect(a1).beCloseToWithin(a2, 0.01);
-
- // Manual test again for Apple's API
- CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
- NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, nil);
- NSUInteger exifOrientation = [properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
- CFRelease(source);
- expect(exifOrientation).equal(kCGImagePropertyOrientationDown);
- }
- - (void)test30ThatImageIOPNGPluginBuggyWorkaround {
- // See: #3634
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"IndexedPNG" withExtension:@"png"];
- NSData *data = [NSData dataWithContentsOfURL:url];
-
- UIImage *decodedImage = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil];
- UIColor *testColor1 = [decodedImage sd_colorAtPoint:CGPointMake(100, 1)];
- CGFloat r1, g1, b1, a1;
- [testColor1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
- expect(r1).beCloseToWithin(0.60, 0.01);
- expect(g1).beCloseToWithin(0.91, 0.01);
- expect(b1).beCloseToWithin(0.91, 0.01);
- expect(a1).beCloseToWithin(0.20, 0.01);
-
- // RGBA 16 bits PNG should not workaround
- url = [[NSBundle bundleForClass:[self class]] URLForResource:@"RGBA16PNG" withExtension:@"png"];
- data = [NSData dataWithContentsOfURL:url];
- decodedImage = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil];
- testColor1 = [decodedImage sd_colorAtPoint:CGPointMake(100, 1)];
- [testColor1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
- expect(r1).beCloseToWithin(0.60, 0.01);
- expect(g1).beCloseToWithin(0.60, 0.01);
- expect(b1).beCloseToWithin(0.33, 0.01);
- expect(a1).beCloseToWithin(0.33, 0.01);
- }
- - (void)test31ThatSVGShouldUseNativeImageClass {
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"svg"];
- NSData *data = [NSData dataWithContentsOfURL:url];
- SDAnimatedImage *animatedImage = [SDAnimatedImage imageWithData:data];
- expect(animatedImage).beNil();
- UIImage *image = [UIImage sd_imageWithData:data];
- Class SVGCoderClass = NSClassFromString(@"SDImageSVGCoder");
- if (SVGCoderClass && [SVGCoderClass sharedCoder]) {
- expect(image).notTo.beNil();
- // Vector version
- expect(image.sd_isVector).beTruthy();
- } else {
- // Platform does not support SVG
- expect(image).beNil();
- }
- }
- - (void)test32ThatHDRDecodeWorks {
- // Only test for iOS 17+/macOS 14+/visionOS 1+, or ImageIO decoder does not support HDR
- #if SD_MAC || SD_IOS || SD_VISION
- if (@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)) {
- NSArray *formats = @[@"heic", @"avif", @"jxl"];
- for (NSString *format in formats) {
- NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestHDR" withExtension:format];
- NSData *data = [NSData dataWithContentsOfURL:url];
- // Decoding
- UIImage *HDRImage = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeToHDR : @(YES)}];
- UIImage *SDRImage = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeToHDR : @(NO)}];
-
- expect(HDRImage).notTo.beNil();
- expect(SDRImage).notTo.beNil();
-
- expect([SDImageCoderHelper CGImageIsHDR:HDRImage.CGImage]).beTruthy();
- expect(HDRImage.sd_isHighDynamicRange).beTruthy();
- // FIXME: on Simulator, the SDR decode options will not take effect, so SDR is the same as HDR
- #if !TARGET_OS_SIMULATOR
- expect([SDImageCoderHelper CGImageIsHDR:SDRImage.CGImage]).beFalsy();
- expect(SDRImage.sd_isHighDynamicRange).beFalsy();
- #endif
- // FIXME: Encoding need iOS 18+/macOS 15+
- // And need test both GainMap HDR or ISO HDR, TODO
- }
- }
- #endif
- }
- #pragma mark - Utils
- - (void)verifyCoder:(id<SDImageCoder>)coder
- withLocalImageURL:(NSURL *)imageUrl
- supportsEncoding:(BOOL)supportsEncoding
- isAnimatedImage:(BOOL)isAnimated {
- [self verifyCoder:coder withLocalImageURL:imageUrl supportsEncoding:supportsEncoding encodingFormat:SDImageFormatUndefined isAnimatedImage:isAnimated isVectorImage:NO];
- }
- - (void)verifyCoder:(id<SDImageCoder>)coder
- withLocalImageURL:(NSURL *)imageUrl
- supportsEncoding:(BOOL)supportsEncoding
- encodingFormat:(SDImageFormat)encodingFormat
- isAnimatedImage:(BOOL)isAnimated
- isVectorImage:(BOOL)isVector {
- NSData *inputImageData = [NSData dataWithContentsOfURL:imageUrl];
- expect(inputImageData).toNot.beNil();
- SDImageFormat inputImageFormat = [NSData sd_imageFormatForImageData:inputImageData];
- expect(inputImageFormat).toNot.equal(SDImageFormatUndefined);
-
- // 1 - check if we can decode - should be true
- expect([coder canDecodeFromData:inputImageData]).to.beTruthy();
-
- // 2 - decode from NSData to UIImage and check it
- UIImage *inputImage = [coder decodedImageWithData:inputImageData options:nil];
- expect(inputImage).toNot.beNil();
-
- if (isAnimated) {
- // 2a - check images count > 0 (only for animated images)
- expect(inputImage.sd_isAnimated).to.beTruthy();
-
- // 2b - check image size and scale for each frameImage (only for animated images)
- #if SD_UIKIT
- CGSize imageSize = inputImage.size;
- CGFloat imageScale = inputImage.scale;
- [inputImage.images enumerateObjectsUsingBlock:^(UIImage * frameImage, NSUInteger idx, BOOL * stop) {
- expect(imageSize).to.equal(frameImage.size);
- expect(imageScale).to.equal(frameImage.scale);
- }];
- #endif
- }
-
- // 3 - check thumbnail decoding
- CGFloat pixelWidth = inputImage.size.width;
- CGFloat pixelHeight = inputImage.size.height;
- expect(pixelWidth).beGreaterThan(0);
- expect(pixelHeight).beGreaterThan(0);
- // check vector format should use 72 DPI
- if (isVector) {
- CGRect boxRect = [self boxRectFromPDFData:inputImageData];
- expect(boxRect.size.width).beGreaterThan(0);
- expect(boxRect.size.height).beGreaterThan(0);
- // Since 72 DPI is 1:1 from inch size to pixel size
- expect(boxRect.size.width).equal(pixelWidth);
- expect(boxRect.size.height).equal(pixelHeight);
- }
-
- // check thumbnail with scratch
- CGFloat thumbnailWidth = 50;
- CGFloat thumbnailHeight = 50;
- UIImage *thumbImage = [coder decodedImageWithData:inputImageData options:@{
- SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
- SDImageCoderDecodePreserveAspectRatio : @(NO)
- }];
- expect(thumbImage).toNot.beNil();
- expect(thumbImage.size).equal(CGSizeMake(thumbnailWidth, thumbnailHeight));
- // check thumbnail with aspect ratio limit
- thumbImage = [coder decodedImageWithData:inputImageData options:@{
- SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
- SDImageCoderDecodePreserveAspectRatio : @(YES)
- }];
- expect(thumbImage).toNot.beNil();
- CGFloat ratio = pixelWidth / pixelHeight;
- CGFloat thumbnailRatio = thumbnailWidth / thumbnailHeight;
- CGSize thumbnailPixelSize;
- if (ratio > thumbnailRatio) {
- thumbnailPixelSize = CGSizeMake(thumbnailWidth, round(thumbnailWidth / ratio));
- } else {
- thumbnailPixelSize = CGSizeMake(round(thumbnailHeight * ratio), thumbnailHeight);
- }
- // Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
- expect(ABS(thumbImage.size.width - thumbnailPixelSize.width)).beLessThanOrEqualTo(1);
- expect(ABS(thumbImage.size.height - thumbnailPixelSize.height)).beLessThanOrEqualTo(1);
-
-
- if (supportsEncoding) {
- // 4 - check if we can encode to the original format
- if (encodingFormat == SDImageFormatUndefined) {
- encodingFormat = inputImageFormat;
- }
- expect([coder canEncodeToFormat:encodingFormat]).to.beTruthy();
-
- // 5 - encode from UIImage to NSData using the inputImageFormat and check it
- NSData *outputImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:nil];
- expect(outputImageData).toNot.beNil();
- UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
- expect(outputImage.size).to.equal(inputImage.size);
- expect(outputImage.scale).to.equal(inputImage.scale);
- expect(outputImage.sd_imageLoopCount).to.equal(inputImage.sd_imageLoopCount);
-
- // check max pixel size encoding with scratch
- CGFloat maxWidth = 50;
- CGFloat maxHeight = 50;
- CGFloat maxRatio = maxWidth / maxHeight;
- CGSize maxPixelSize;
- if (ratio > maxRatio) {
- maxPixelSize = CGSizeMake(maxWidth, round(maxWidth / ratio));
- } else {
- maxPixelSize = CGSizeMake(round(maxHeight * ratio), maxHeight);
- }
- NSData *outputMaxImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:@{SDImageCoderEncodeMaxPixelSize : @(CGSizeMake(maxWidth, maxHeight))}];
- UIImage *outputMaxImage = [coder decodedImageWithData:outputMaxImageData options:nil];
- // Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
- expect(ABS(outputMaxImage.size.width - maxPixelSize.width)).beLessThanOrEqualTo(1);
- expect(ABS(outputMaxImage.size.height - maxPixelSize.height)).beLessThanOrEqualTo(1);
- expect(outputMaxImage.sd_imageLoopCount).to.equal(inputImage.sd_imageLoopCount);
- }
- }
- - (NSArray *)thumbnailImagesFromImageSource:(CGImageSourceRef)source API_AVAILABLE(ios(11.0), tvos(11.0), macos(10.13)) {
- NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
- NSDictionary *fileProperties = properties[(__bridge NSString *)kCGImagePropertyFileContentsDictionary];
- NSArray *imagesProperties = fileProperties[(__bridge NSString *)kCGImagePropertyImages];
- NSDictionary *imageProperties = imagesProperties.firstObject;
- NSArray *thumbnailImages = imageProperties[(__bridge NSString *)kCGImagePropertyThumbnailImages];
-
- return thumbnailImages;
- }
- #pragma mark - Utils
- - (CGRect)boxRectFromPDFData:(nonnull NSData *)data {
- CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
- if (!provider) {
- return CGRectZero;
- }
- CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
- CGDataProviderRelease(provider);
- if (!document) {
- return CGRectZero;
- }
-
- // `CGPDFDocumentGetPage` page number is 1-indexed.
- CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
- if (!page) {
- CGPDFDocumentRelease(document);
- return CGRectZero;
- }
-
- CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
- CGPDFDocumentRelease(document);
-
- return boxRect;
- }
- @end
|