fix: Fix memory leak and add image quality option
This commit is contained in:
parent
100690b568
commit
5a9842cdeb
19
ffigen.dart
19
ffigen.dart
|
|
@ -17,6 +17,8 @@ final config = FfiGenerator(
|
|||
'CFDataCreateMutable',
|
||||
'CFDataGetBytePtr',
|
||||
'CFDataGetLength',
|
||||
// CFDictionary operations
|
||||
'CFDictionaryCreate',
|
||||
// CGImageSource operations (decoding)
|
||||
'CGImageSourceCreateWithData',
|
||||
'CGImageSourceCreateImageAtIndex',
|
||||
|
|
@ -24,8 +26,23 @@ final config = FfiGenerator(
|
|||
'CGImageDestinationCreateWithData',
|
||||
'CGImageDestinationAddImage',
|
||||
'CGImageDestinationFinalize',
|
||||
// Memory management
|
||||
'CFRelease',
|
||||
}),
|
||||
globals: Globals.includeSet({
|
||||
'kCFAllocatorDefault',
|
||||
'kCGImageDestinationLossyCompressionQuality',
|
||||
'kCFTypeDictionaryValueCallBacks',
|
||||
'kCFTypeDictionaryKeyCallBacks',
|
||||
}),
|
||||
typedefs: Typedefs.includeSet({
|
||||
'CFDataRef',
|
||||
'CFDictionaryRef',
|
||||
'CGImageRef',
|
||||
'CGImageSourceRef',
|
||||
'CFMutableDataRef',
|
||||
'CGImageDestinationRef',
|
||||
}),
|
||||
globals: Globals.includeSet({'kCFAllocatorDefault'}),
|
||||
);
|
||||
|
||||
void main() => config.generate();
|
||||
|
|
|
|||
|
|
@ -8,98 +8,210 @@ import 'package:objective_c/objective_c.dart' as objc;
|
|||
@ffi.Native<ffi.Pointer<__CFAllocator>>()
|
||||
external final ffi.Pointer<__CFAllocator> kCFAllocatorDefault;
|
||||
|
||||
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>()
|
||||
external void CFRelease(ffi.Pointer<ffi.Void> cf);
|
||||
|
||||
@ffi.Native<CFDictionaryKeyCallBacks>()
|
||||
external final CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks;
|
||||
|
||||
@ffi.Native<CFDictionaryValueCallBacks>()
|
||||
external final CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks;
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Pointer<__CFData> Function(
|
||||
CFDictionaryRef Function(
|
||||
ffi.Pointer<__CFAllocator>,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Void>>,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Void>>,
|
||||
ffi.Long,
|
||||
ffi.Pointer<CFDictionaryKeyCallBacks>,
|
||||
ffi.Pointer<CFDictionaryValueCallBacks>,
|
||||
)
|
||||
>()
|
||||
external CFDictionaryRef CFDictionaryCreate(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Void>> keys,
|
||||
ffi.Pointer<ffi.Pointer<ffi.Void>> values,
|
||||
int numValues,
|
||||
ffi.Pointer<CFDictionaryKeyCallBacks> keyCallBacks,
|
||||
ffi.Pointer<CFDictionaryValueCallBacks> valueCallBacks,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
CFDataRef Function(
|
||||
ffi.Pointer<__CFAllocator>,
|
||||
ffi.Pointer<ffi.UnsignedChar>,
|
||||
ffi.Long,
|
||||
)
|
||||
>()
|
||||
external ffi.Pointer<__CFData> CFDataCreate(
|
||||
external CFDataRef CFDataCreate(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.UnsignedChar> bytes,
|
||||
int length,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Pointer<__CFData> Function(ffi.Pointer<__CFAllocator>, ffi.Long)
|
||||
>()
|
||||
external ffi.Pointer<__CFData> CFDataCreateMutable(
|
||||
@ffi.Native<CFMutableDataRef Function(ffi.Pointer<__CFAllocator>, ffi.Long)>()
|
||||
external CFMutableDataRef CFDataCreateMutable(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
int capacity,
|
||||
);
|
||||
|
||||
@ffi.Native<ffi.Long Function(ffi.Pointer<__CFData>)>()
|
||||
external int CFDataGetLength(ffi.Pointer<__CFData> theData);
|
||||
@ffi.Native<ffi.Long Function(CFDataRef)>()
|
||||
external int CFDataGetLength(CFDataRef theData);
|
||||
|
||||
@ffi.Native<ffi.Pointer<ffi.UnsignedChar> Function(ffi.Pointer<__CFData>)>()
|
||||
external ffi.Pointer<ffi.UnsignedChar> CFDataGetBytePtr(
|
||||
ffi.Pointer<__CFData> theData,
|
||||
@ffi.Native<ffi.Pointer<ffi.UnsignedChar> Function(CFDataRef)>()
|
||||
external ffi.Pointer<ffi.UnsignedChar> CFDataGetBytePtr(CFDataRef theData);
|
||||
|
||||
@ffi.Native<CGImageSourceRef Function(CFDataRef, CFDictionaryRef)>()
|
||||
external CGImageSourceRef CGImageSourceCreateWithData(
|
||||
CFDataRef data,
|
||||
CFDictionaryRef options,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Pointer<CGImageSource> Function(
|
||||
ffi.Pointer<__CFData>,
|
||||
ffi.Pointer<__CFDictionary>,
|
||||
)
|
||||
>()
|
||||
external ffi.Pointer<CGImageSource> CGImageSourceCreateWithData(
|
||||
ffi.Pointer<__CFData> data,
|
||||
ffi.Pointer<__CFDictionary> options,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Pointer<CGImage> Function(
|
||||
ffi.Pointer<CGImageSource>,
|
||||
ffi.Size,
|
||||
ffi.Pointer<__CFDictionary>,
|
||||
)
|
||||
>()
|
||||
external ffi.Pointer<CGImage> CGImageSourceCreateImageAtIndex(
|
||||
ffi.Pointer<CGImageSource> isrc,
|
||||
@ffi.Native<CGImageRef Function(CGImageSourceRef, ffi.Size, CFDictionaryRef)>()
|
||||
external CGImageRef CGImageSourceCreateImageAtIndex(
|
||||
CGImageSourceRef isrc,
|
||||
int index,
|
||||
ffi.Pointer<__CFDictionary> options,
|
||||
CFDictionaryRef options,
|
||||
);
|
||||
|
||||
@ffi.Native<ffi.Pointer<objc.CFString>>()
|
||||
external ffi.Pointer<objc.CFString> kCGImageDestinationLossyCompressionQuality;
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Pointer<CGImageDestination> Function(
|
||||
ffi.Pointer<__CFData>,
|
||||
CGImageDestinationRef Function(
|
||||
CFMutableDataRef,
|
||||
ffi.Pointer<objc.CFString>,
|
||||
ffi.Size,
|
||||
ffi.Pointer<__CFDictionary>,
|
||||
CFDictionaryRef,
|
||||
)
|
||||
>()
|
||||
external ffi.Pointer<CGImageDestination> CGImageDestinationCreateWithData(
|
||||
ffi.Pointer<__CFData> data,
|
||||
external CGImageDestinationRef CGImageDestinationCreateWithData(
|
||||
CFMutableDataRef data,
|
||||
ffi.Pointer<objc.CFString> type,
|
||||
int count,
|
||||
ffi.Pointer<__CFDictionary> options,
|
||||
CFDictionaryRef options,
|
||||
);
|
||||
|
||||
@ffi.Native<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<CGImageDestination>,
|
||||
ffi.Pointer<CGImage>,
|
||||
ffi.Pointer<__CFDictionary>,
|
||||
)
|
||||
ffi.Void Function(CGImageDestinationRef, CGImageRef, CFDictionaryRef)
|
||||
>()
|
||||
external void CGImageDestinationAddImage(
|
||||
ffi.Pointer<CGImageDestination> idst,
|
||||
ffi.Pointer<CGImage> image,
|
||||
ffi.Pointer<__CFDictionary> properties,
|
||||
CGImageDestinationRef idst,
|
||||
CGImageRef image,
|
||||
CFDictionaryRef properties,
|
||||
);
|
||||
|
||||
@ffi.Native<ffi.Bool Function(ffi.Pointer<CGImageDestination>)>()
|
||||
external bool CGImageDestinationFinalize(ffi.Pointer<CGImageDestination> idst);
|
||||
@ffi.Native<ffi.Bool Function(CGImageDestinationRef)>()
|
||||
external bool CGImageDestinationFinalize(CGImageDestinationRef idst);
|
||||
|
||||
final class __CFAllocator extends ffi.Opaque {}
|
||||
|
||||
final class CFDictionaryKeyCallBacks extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int version;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Void> Function(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.Void> value,
|
||||
)
|
||||
>
|
||||
>
|
||||
retain;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.Void> value,
|
||||
)
|
||||
>
|
||||
>
|
||||
release;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<objc.CFString> Function(ffi.Pointer<ffi.Void> value)
|
||||
>
|
||||
>
|
||||
copyDescription;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.UnsignedChar Function(
|
||||
ffi.Pointer<ffi.Void> value1,
|
||||
ffi.Pointer<ffi.Void> value2,
|
||||
)
|
||||
>
|
||||
>
|
||||
equal;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<ffi.UnsignedLong Function(ffi.Pointer<ffi.Void> value)>
|
||||
>
|
||||
hash;
|
||||
}
|
||||
|
||||
final class CFDictionaryValueCallBacks extends ffi.Struct {
|
||||
@ffi.Long()
|
||||
external int version;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Void> Function(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.Void> value,
|
||||
)
|
||||
>
|
||||
>
|
||||
retain;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<__CFAllocator> allocator,
|
||||
ffi.Pointer<ffi.Void> value,
|
||||
)
|
||||
>
|
||||
>
|
||||
release;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<objc.CFString> Function(ffi.Pointer<ffi.Void> value)
|
||||
>
|
||||
>
|
||||
copyDescription;
|
||||
|
||||
external ffi.Pointer<
|
||||
ffi.NativeFunction<
|
||||
ffi.UnsignedChar Function(
|
||||
ffi.Pointer<ffi.Void> value1,
|
||||
ffi.Pointer<ffi.Void> value2,
|
||||
)
|
||||
>
|
||||
>
|
||||
equal;
|
||||
}
|
||||
|
||||
final class __CFDictionary extends ffi.Opaque {}
|
||||
|
||||
typedef CFDictionaryRef = ffi.Pointer<__CFDictionary>;
|
||||
|
||||
final class __CFData extends ffi.Opaque {}
|
||||
|
||||
typedef CFDataRef = ffi.Pointer<__CFData>;
|
||||
typedef CFMutableDataRef = ffi.Pointer<__CFData>;
|
||||
|
||||
final class CGImageSource extends ffi.Opaque {}
|
||||
|
||||
typedef CGImageSourceRef = ffi.Pointer<CGImageSource>;
|
||||
|
||||
final class CGImage extends ffi.Opaque {}
|
||||
|
||||
typedef CGImageRef = ffi.Pointer<CGImage>;
|
||||
|
||||
final class CGImageDestination extends ffi.Opaque {}
|
||||
|
||||
typedef CGImageDestinationRef = ffi.Pointer<CGImageDestination>;
|
||||
|
|
|
|||
|
|
@ -35,11 +35,18 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
|||
OutputFormat format = OutputFormat.jpeg,
|
||||
int quality = 100,
|
||||
}) async {
|
||||
final inputPtr = calloc<Uint8>(inputData.length);
|
||||
Pointer<Uint8>? inputPtr;
|
||||
CFDataRef? cfData;
|
||||
CGImageSourceRef? imageSource;
|
||||
CGImageRef? cgImage;
|
||||
CFMutableDataRef? outputData;
|
||||
CGImageDestinationRef? destination;
|
||||
CFDictionaryRef? properties;
|
||||
try {
|
||||
inputPtr = calloc<Uint8>(inputData.length);
|
||||
inputPtr.asTypedList(inputData.length).setAll(0, inputData);
|
||||
|
||||
final cfData = CFDataCreate(
|
||||
cfData = CFDataCreate(
|
||||
kCFAllocatorDefault,
|
||||
inputPtr.cast(),
|
||||
inputData.length,
|
||||
|
|
@ -48,17 +55,17 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
|||
throw Exception('Failed to create CFData from input data.');
|
||||
}
|
||||
|
||||
final imageSource = CGImageSourceCreateWithData(cfData, nullptr);
|
||||
imageSource = CGImageSourceCreateWithData(cfData, nullptr);
|
||||
if (imageSource == nullptr) {
|
||||
throw Exception('Failed to create CGImageSource. Invalid image data.');
|
||||
}
|
||||
|
||||
final cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
|
||||
cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
|
||||
if (cgImage == nullptr) {
|
||||
throw Exception('Failed to decode image.');
|
||||
}
|
||||
|
||||
final outputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
||||
outputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
||||
if (outputData == nullptr) {
|
||||
throw Exception('Failed to create output CFData.');
|
||||
}
|
||||
|
|
@ -81,7 +88,7 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
|||
.retainAndAutorelease()
|
||||
.cast<CFString>();
|
||||
|
||||
final destination = CGImageDestinationCreateWithData(
|
||||
destination = CGImageDestinationCreateWithData(
|
||||
outputData,
|
||||
cfString,
|
||||
1,
|
||||
|
|
@ -91,7 +98,8 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
|||
throw Exception('Failed to create CGImageDestination.');
|
||||
}
|
||||
|
||||
CGImageDestinationAddImage(destination, cgImage, nullptr);
|
||||
properties = _createPropertiesForFormat(format, quality);
|
||||
CGImageDestinationAddImage(destination, cgImage, properties ?? nullptr);
|
||||
|
||||
final success = CGImageDestinationFinalize(destination);
|
||||
if (!success) {
|
||||
|
|
@ -106,7 +114,45 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
|||
|
||||
return Uint8List.fromList(bytePtr.cast<Uint8>().asTypedList(length));
|
||||
} finally {
|
||||
calloc.free(inputPtr);
|
||||
if (inputPtr != null) calloc.free(inputPtr);
|
||||
if (cfData != null) CFRelease(cfData.cast());
|
||||
if (imageSource != null) CFRelease(imageSource.cast());
|
||||
if (cgImage != null) CFRelease(cgImage.cast());
|
||||
if (outputData != null) CFRelease(outputData.cast());
|
||||
if (destination != null) CFRelease(destination.cast());
|
||||
if (properties != null) CFRelease(properties.cast());
|
||||
}
|
||||
}
|
||||
|
||||
CFDictionaryRef? _createPropertiesForFormat(
|
||||
OutputFormat format,
|
||||
int quality,
|
||||
) {
|
||||
if (format == OutputFormat.png || format == OutputFormat.webp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return using((arena) {
|
||||
final keys = arena<Pointer<CFString>>(1);
|
||||
final values = arena<Pointer<Void>>(1);
|
||||
|
||||
keys[0] = kCGImageDestinationLossyCompressionQuality;
|
||||
values[0] = (quality / 100.0)
|
||||
.toNSNumber()
|
||||
.ref
|
||||
.retainAndAutorelease()
|
||||
.cast<Void>();
|
||||
|
||||
final keyCallBacks = arena<CFDictionaryKeyCallBacks>();
|
||||
final valueCallBacks = arena<CFDictionaryValueCallBacks>();
|
||||
return CFDictionaryCreate(
|
||||
kCFAllocatorDefault,
|
||||
keys.cast(),
|
||||
values.cast(),
|
||||
1,
|
||||
keyCallBacks,
|
||||
valueCallBacks,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue