diff --git a/lib/src/darwin/bindings.g.dart b/lib/src/darwin/bindings.g.dart index d2fea58..e1be9d9 100644 --- a/lib/src/darwin/bindings.g.dart +++ b/lib/src/darwin/bindings.g.dart @@ -130,61 +130,6 @@ external CGImageRef CGImageSourceCreateImageAtIndex( CFDictionaryRef options, ); -/// Copies the properties dictionary for the image at [index] inside [isrc]. -/// Returns a retained CFDictionaryRef — caller must CFRelease. -@ffi.Native() -external CFDictionaryRef CGImageSourceCopyPropertiesAtIndex( - CGImageSourceRef isrc, - int index, - CFDictionaryRef options, -); - -/// Creates a thumbnail for the image at [index] inside [isrc] using [options]. -/// Setting kCGImageSourceCreateThumbnailWithTransform bakes EXIF orientation. -/// Returns a retained CGImageRef — caller must CFRelease. -@ffi.Native() -external CGImageRef CGImageSourceCreateThumbnailAtIndex( - CGImageSourceRef isrc, - int index, - CFDictionaryRef options, -); - -/// Returns the value associated with [key] in [theDict], or nullptr. -@ffi.Native Function(CFDictionaryRef, ffi.Pointer)>() -external ffi.Pointer CFDictionaryGetValue( - CFDictionaryRef theDict, - ffi.Pointer key, -); - -/// Extracts the numeric value from a CFNumber into [valuePtr]. -/// [theType] 9 = kCFNumberInt32Type. -@ffi.Native, ffi.Int32, ffi.Pointer)>() -external bool CFNumberGetValue( - ffi.Pointer number, - int theType, - ffi.Pointer valuePtr, -); - -/// EXIF/CGImage orientation tag key in CGImageSource properties dictionaries. -@ffi.Native>() -external ffi.Pointer kCGImagePropertyOrientation; - -/// When kCFBooleanTrue, creates thumbnail even if none is embedded, using -/// kCGImageSourceThumbnailMaxPixelSize to determine the output dimensions. -@ffi.Native>() -external ffi.Pointer - kCGImageSourceCreateThumbnailFromImageIfAbsent; - -/// When kCFBooleanTrue, rotates/flips the thumbnail to apply the EXIF -/// orientation tag, producing correctly-oriented pixel data. -@ffi.Native>() -external ffi.Pointer kCGImageSourceCreateThumbnailWithTransform; - -/// Maximum pixel size (longest edge) for the thumbnail returned by -/// CGImageSourceCreateThumbnailAtIndex. -@ffi.Native>() -external ffi.Pointer kCGImageSourceThumbnailMaxPixelSize; - @ffi.Native>() external ffi.Pointer kCGImageDestinationLossyCompressionQuality; diff --git a/lib/src/darwin/native.dart b/lib/src/darwin/native.dart index eb5319f..10d272b 100644 --- a/lib/src/darwin/native.dart +++ b/lib/src/darwin/native.dart @@ -18,13 +18,10 @@ import 'package:platform_image_converter/src/output_resize.dart'; /// - Supports all major image formats (JPEG, PNG, HEIC, WebP, etc.) /// - Uses CoreFoundation and CoreGraphics for efficient processing /// - Memory-safe with proper resource cleanup -/// - Applies EXIF orientation correction: bakes rotation/flip into pixels so -/// output images display upright regardless of EXIF tag presence. /// /// **API Stack:** /// - `CGImageSourceCreateWithData`: Decode input image -/// - `CGImageSourceCopyPropertiesAtIndex`: Read EXIF orientation tag -/// - `CGImageSourceCreateThumbnailAtIndex`: Create orientation-corrected image +/// - `CGImageSourceCreateImageAtIndex`: Extract CGImage /// - `CGBitmapContextCreate`: Create a canvas for resizing /// - `CGContextDrawImage`: Draw and scale the image /// - `CGBitmapContextCreateImage`: Extract resized CGImage @@ -50,8 +47,6 @@ final class ImageConverterDarwin implements ImageConverterPlatform { CFDataRef? cfData; CGImageSourceRef? imageSource; CGImageRef? originalImage; - // Orientation-corrected image (owned, non-null only when correction applied). - CGImageRef? orientedImage; CGImageRef? imageToEncode; CFMutableDataRef? outputData; CGImageDestinationRef? destination; @@ -81,35 +76,17 @@ final class ImageConverterDarwin implements ImageConverterPlatform { throw const ImageDecodingException(); } - // CGImageSourceCreateImageAtIndex returns raw sensor pixels without - // applying the EXIF Orientation tag. iPhone photos captured in portrait - // are stored with orientation=6 (Right/90° CW), causing the decoded - // pixels to appear rotated. We correct this by reading the orientation - // and, when it is not Up (1), using CGImageSourceCreateThumbnailAtIndex - // with kCGImageSourceCreateThumbnailWithTransform=true, which bakes the - // rotation/flip into the pixel data before we encode. - final sourceOrientation = _readOrientationFromSource(imageSource); - if (sourceOrientation != 1) { - final srcW = CGImageGetWidth(originalImage); - final srcH = CGImageGetHeight(originalImage); - final maxEdge = srcW > srcH ? srcW : srcH; - orientedImage = _createOrientedImage(imageSource, maxEdge); - } - - // Use the orientation-corrected image as the base for all further ops. - final baseImage = orientedImage ?? originalImage; - - final originalWidth = CGImageGetWidth(baseImage); - final originalHeight = CGImageGetHeight(baseImage); + final originalWidth = CGImageGetWidth(originalImage); + final originalHeight = CGImageGetHeight(originalImage); final (newWidth, newHeight) = resizeMode.calculateSize( originalWidth, originalHeight, ); if (newWidth == originalWidth && newHeight == originalHeight) { - imageToEncode = baseImage; + imageToEncode = originalImage; } else { - imageToEncode = _resizeImage(baseImage, newWidth, newHeight); + imageToEncode = _resizeImage(originalImage, newWidth, newHeight); } if (imageToEncode == nullptr) { @@ -183,16 +160,9 @@ final class ImageConverterDarwin implements ImageConverterPlatform { if (inputPtr != null) calloc.free(inputPtr); if (cfData != null) CFRelease(cfData.cast()); if (imageSource != null) CFRelease(imageSource.cast()); - // Release imageToEncode only if it is a distinct CGImage (i.e. came from - // _resizeImage). Do not double-release if it was aliased to baseImage. - if (imageToEncode != null && - imageToEncode != orientedImage && - imageToEncode != originalImage) { + if (imageToEncode != null && imageToEncode != originalImage) { CFRelease(imageToEncode.cast()); } - // orientedImage is an independent retained CGImageRef created by - // _createOrientedImage; release it after imageToEncode. - if (orientedImage != null) CFRelease(orientedImage.cast()); if (originalImage != null) CFRelease(originalImage.cast()); if (outputData != null) CFRelease(outputData.cast()); if (destination != null) CFRelease(destination.cast()); @@ -298,84 +268,4 @@ final class ImageConverterDarwin implements ImageConverterPlatform { } } } - - /// Reads the EXIF/CGImage orientation tag from the first image in [src]. - /// - /// Orientation values match CGImagePropertyOrientation: - /// - 1 (Up) = normal, no correction needed - /// - 6 (Right) = 90° CW — most common for portrait iPhone photos - /// - 3 (Down) = 180° - /// - 8 (Left) = 90° CCW - /// - /// Returns 1 if the tag is absent, unreadable, or the image is already upright. - int _readOrientationFromSource(CGImageSourceRef src) { - CFDictionaryRef? props; - try { - props = CGImageSourceCopyPropertiesAtIndex(src, 0, nullptr); - if (props == nullptr) return 1; - final orientRef = CFDictionaryGetValue( - props, - kCGImagePropertyOrientation.cast(), - ); - if (orientRef == nullptr) return 1; - return using((arena) { - final intPtr = arena(); - // kCFNumberInt32Type = 9 - final ok = CFNumberGetValue(orientRef, 9, intPtr.cast()); - return ok ? intPtr.value : 1; - }); - } finally { - if (props != null && props != nullptr) CFRelease(props.cast()); - } - } - - /// Creates a new CGImageRef with EXIF orientation baked into pixel data. - /// - /// Uses [CGImageSourceCreateThumbnailAtIndex] with - /// `kCGImageSourceCreateThumbnailWithTransform = true`, which instructs - /// ImageIO to apply the EXIF rotation/flip before returning the image. - /// [maxPixelSize] should be the longest edge of the source image so the - /// returned image is full resolution (or at least as large as the source). - /// - /// Returns a retained [CGImageRef] that the caller must [CFRelease], - /// or `null` if the thumbnail could not be created. - CGImageRef? _createOrientedImage(CGImageSourceRef src, int maxPixelSize) { - return using((arena) { - final keys = arena>(3); - final values = arena>(3); - - // kCGImageSourceCreateThumbnailFromImageIfAbsent = kCFBooleanTrue - keys[0] = kCGImageSourceCreateThumbnailFromImageIfAbsent.cast(); - values[0] = true.toNSNumber().ref.retainAndAutorelease().cast(); - - // kCGImageSourceCreateThumbnailWithTransform = kCFBooleanTrue - // This is the key that bakes the EXIF rotation into pixel data. - keys[1] = kCGImageSourceCreateThumbnailWithTransform.cast(); - values[1] = true.toNSNumber().ref.retainAndAutorelease().cast(); - - // kCGImageSourceThumbnailMaxPixelSize = longest source edge - // Setting this to the source's longest edge returns full resolution. - keys[2] = kCGImageSourceThumbnailMaxPixelSize.cast(); - values[2] = maxPixelSize.toNSNumber().ref.retainAndAutorelease().cast(); - - final keyCallbacks = arena(); - final valueCallbacks = arena(); - final opts = CFDictionaryCreate( - kCFAllocatorDefault, - keys.cast(), - values.cast(), - 3, - keyCallbacks, - valueCallbacks, - ); - if (opts == nullptr) return null; - - try { - final thumb = CGImageSourceCreateThumbnailAtIndex(src, 0, opts); - return (thumb == nullptr) ? null : thumb; - } finally { - CFRelease(opts.cast()); - } - }); - } } diff --git a/pubspec.yaml b/pubspec.yaml index 799bd3e..1af3e22 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: platform_image_converter description: "A high-performance Flutter plugin for cross-platform image format conversion using native APIs." -version: 1.0.7 +version: 1.0.6 homepage: https://github.com/koji-1009/platform_image_converter topics: - image