Merge pull request #2 from koji-1009/fix/memory_leak
Fix memory leak and code cleanup
This commit is contained in:
commit
05f2d1029a
19
ffigen.dart
19
ffigen.dart
|
|
@ -17,6 +17,8 @@ final config = FfiGenerator(
|
||||||
'CFDataCreateMutable',
|
'CFDataCreateMutable',
|
||||||
'CFDataGetBytePtr',
|
'CFDataGetBytePtr',
|
||||||
'CFDataGetLength',
|
'CFDataGetLength',
|
||||||
|
// CFDictionary operations
|
||||||
|
'CFDictionaryCreate',
|
||||||
// CGImageSource operations (decoding)
|
// CGImageSource operations (decoding)
|
||||||
'CGImageSourceCreateWithData',
|
'CGImageSourceCreateWithData',
|
||||||
'CGImageSourceCreateImageAtIndex',
|
'CGImageSourceCreateImageAtIndex',
|
||||||
|
|
@ -24,8 +26,23 @@ final config = FfiGenerator(
|
||||||
'CGImageDestinationCreateWithData',
|
'CGImageDestinationCreateWithData',
|
||||||
'CGImageDestinationAddImage',
|
'CGImageDestinationAddImage',
|
||||||
'CGImageDestinationFinalize',
|
'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();
|
void main() => config.generate();
|
||||||
|
|
|
||||||
|
|
@ -8,98 +8,210 @@ import 'package:objective_c/objective_c.dart' as objc;
|
||||||
@ffi.Native<ffi.Pointer<__CFAllocator>>()
|
@ffi.Native<ffi.Pointer<__CFAllocator>>()
|
||||||
external final ffi.Pointer<__CFAllocator> kCFAllocatorDefault;
|
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.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<__CFAllocator>,
|
||||||
ffi.Pointer<ffi.UnsignedChar>,
|
ffi.Pointer<ffi.UnsignedChar>,
|
||||||
ffi.Long,
|
ffi.Long,
|
||||||
)
|
)
|
||||||
>()
|
>()
|
||||||
external ffi.Pointer<__CFData> CFDataCreate(
|
external CFDataRef CFDataCreate(
|
||||||
ffi.Pointer<__CFAllocator> allocator,
|
ffi.Pointer<__CFAllocator> allocator,
|
||||||
ffi.Pointer<ffi.UnsignedChar> bytes,
|
ffi.Pointer<ffi.UnsignedChar> bytes,
|
||||||
int length,
|
int length,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<CFMutableDataRef Function(ffi.Pointer<__CFAllocator>, ffi.Long)>()
|
||||||
ffi.Pointer<__CFData> Function(ffi.Pointer<__CFAllocator>, ffi.Long)
|
external CFMutableDataRef CFDataCreateMutable(
|
||||||
>()
|
|
||||||
external ffi.Pointer<__CFData> CFDataCreateMutable(
|
|
||||||
ffi.Pointer<__CFAllocator> allocator,
|
ffi.Pointer<__CFAllocator> allocator,
|
||||||
int capacity,
|
int capacity,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<ffi.Long Function(ffi.Pointer<__CFData>)>()
|
@ffi.Native<ffi.Long Function(CFDataRef)>()
|
||||||
external int CFDataGetLength(ffi.Pointer<__CFData> theData);
|
external int CFDataGetLength(CFDataRef theData);
|
||||||
|
|
||||||
@ffi.Native<ffi.Pointer<ffi.UnsignedChar> Function(ffi.Pointer<__CFData>)>()
|
@ffi.Native<ffi.Pointer<ffi.UnsignedChar> Function(CFDataRef)>()
|
||||||
external ffi.Pointer<ffi.UnsignedChar> CFDataGetBytePtr(
|
external ffi.Pointer<ffi.UnsignedChar> CFDataGetBytePtr(CFDataRef theData);
|
||||||
ffi.Pointer<__CFData> theData,
|
|
||||||
|
@ffi.Native<CGImageSourceRef Function(CFDataRef, CFDictionaryRef)>()
|
||||||
|
external CGImageSourceRef CGImageSourceCreateWithData(
|
||||||
|
CFDataRef data,
|
||||||
|
CFDictionaryRef options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<CGImageRef Function(CGImageSourceRef, ffi.Size, CFDictionaryRef)>()
|
||||||
ffi.Pointer<CGImageSource> Function(
|
external CGImageRef CGImageSourceCreateImageAtIndex(
|
||||||
ffi.Pointer<__CFData>,
|
CGImageSourceRef isrc,
|
||||||
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,
|
|
||||||
int index,
|
int index,
|
||||||
ffi.Pointer<__CFDictionary> options,
|
CFDictionaryRef options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ffi.Native<ffi.Pointer<objc.CFString>>()
|
||||||
|
external ffi.Pointer<objc.CFString> kCGImageDestinationLossyCompressionQuality;
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Pointer<CGImageDestination> Function(
|
CGImageDestinationRef Function(
|
||||||
ffi.Pointer<__CFData>,
|
CFMutableDataRef,
|
||||||
ffi.Pointer<objc.CFString>,
|
ffi.Pointer<objc.CFString>,
|
||||||
ffi.Size,
|
ffi.Size,
|
||||||
ffi.Pointer<__CFDictionary>,
|
CFDictionaryRef,
|
||||||
)
|
)
|
||||||
>()
|
>()
|
||||||
external ffi.Pointer<CGImageDestination> CGImageDestinationCreateWithData(
|
external CGImageDestinationRef CGImageDestinationCreateWithData(
|
||||||
ffi.Pointer<__CFData> data,
|
CFMutableDataRef data,
|
||||||
ffi.Pointer<objc.CFString> type,
|
ffi.Pointer<objc.CFString> type,
|
||||||
int count,
|
int count,
|
||||||
ffi.Pointer<__CFDictionary> options,
|
CFDictionaryRef options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
ffi.Void Function(CGImageDestinationRef, CGImageRef, CFDictionaryRef)
|
||||||
ffi.Pointer<CGImageDestination>,
|
|
||||||
ffi.Pointer<CGImage>,
|
|
||||||
ffi.Pointer<__CFDictionary>,
|
|
||||||
)
|
|
||||||
>()
|
>()
|
||||||
external void CGImageDestinationAddImage(
|
external void CGImageDestinationAddImage(
|
||||||
ffi.Pointer<CGImageDestination> idst,
|
CGImageDestinationRef idst,
|
||||||
ffi.Pointer<CGImage> image,
|
CGImageRef image,
|
||||||
ffi.Pointer<__CFDictionary> properties,
|
CFDictionaryRef properties,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<ffi.Bool Function(ffi.Pointer<CGImageDestination>)>()
|
@ffi.Native<ffi.Bool Function(CGImageDestinationRef)>()
|
||||||
external bool CGImageDestinationFinalize(ffi.Pointer<CGImageDestination> idst);
|
external bool CGImageDestinationFinalize(CGImageDestinationRef idst);
|
||||||
|
|
||||||
final class __CFAllocator extends ffi.Opaque {}
|
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 {}
|
final class __CFDictionary extends ffi.Opaque {}
|
||||||
|
|
||||||
|
typedef CFDictionaryRef = ffi.Pointer<__CFDictionary>;
|
||||||
|
|
||||||
final class __CFData extends ffi.Opaque {}
|
final class __CFData extends ffi.Opaque {}
|
||||||
|
|
||||||
|
typedef CFDataRef = ffi.Pointer<__CFData>;
|
||||||
|
typedef CFMutableDataRef = ffi.Pointer<__CFData>;
|
||||||
|
|
||||||
final class CGImageSource extends ffi.Opaque {}
|
final class CGImageSource extends ffi.Opaque {}
|
||||||
|
|
||||||
|
typedef CGImageSourceRef = ffi.Pointer<CGImageSource>;
|
||||||
|
|
||||||
final class CGImage extends ffi.Opaque {}
|
final class CGImage extends ffi.Opaque {}
|
||||||
|
|
||||||
|
typedef CGImageRef = ffi.Pointer<CGImage>;
|
||||||
|
|
||||||
final class CGImageDestination extends ffi.Opaque {}
|
final class CGImageDestination extends ffi.Opaque {}
|
||||||
|
|
||||||
|
typedef CGImageDestinationRef = ffi.Pointer<CGImageDestination>;
|
||||||
|
|
|
||||||
|
|
@ -34,60 +34,46 @@ final class ImageConverterAndroid implements ImageConverterPlatform {
|
||||||
OutputFormat format = OutputFormat.jpeg,
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
int quality = 100,
|
int quality = 100,
|
||||||
}) async {
|
}) async {
|
||||||
final inputJBytes = JByteArray.from(inputData);
|
JByteArray? inputJBytes;
|
||||||
|
Bitmap? bitmap;
|
||||||
|
Bitmap$CompressFormat? compressFormat;
|
||||||
|
ByteArrayOutputStream? outputStream;
|
||||||
|
JByteArray? outputJBytes;
|
||||||
try {
|
try {
|
||||||
final bitmap = BitmapFactory.decodeByteArray(
|
inputJBytes = JByteArray.from(inputData);
|
||||||
inputJBytes,
|
bitmap = BitmapFactory.decodeByteArray(inputJBytes, 0, inputData.length);
|
||||||
0,
|
|
||||||
inputData.length,
|
|
||||||
);
|
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
throw Exception('Failed to decode image. Invalid image data.');
|
throw Exception('Failed to decode image. Invalid image data.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
compressFormat = switch (format) {
|
||||||
final compressFormat = switch (format) {
|
OutputFormat.jpeg => Bitmap$CompressFormat.JPEG,
|
||||||
OutputFormat.jpeg => Bitmap$CompressFormat.JPEG,
|
OutputFormat.png => Bitmap$CompressFormat.PNG,
|
||||||
OutputFormat.png => Bitmap$CompressFormat.PNG,
|
OutputFormat.webp => Bitmap$CompressFormat.WEBP_LOSSY,
|
||||||
OutputFormat.webp => Bitmap$CompressFormat.WEBP_LOSSY,
|
OutputFormat.heic => throw UnsupportedError(
|
||||||
OutputFormat.heic => throw UnsupportedError(
|
'HEIC output format is not supported on Android.',
|
||||||
'HEIC output format is not supported on Android.',
|
),
|
||||||
),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
outputStream = ByteArrayOutputStream();
|
||||||
final outputStream = ByteArrayOutputStream();
|
final success = bitmap.compress(compressFormat, quality, outputStream);
|
||||||
try {
|
if (!success) {
|
||||||
final success = bitmap.compress(
|
throw Exception('Failed to compress bitmap.');
|
||||||
compressFormat,
|
|
||||||
quality,
|
|
||||||
outputStream,
|
|
||||||
);
|
|
||||||
if (!success) {
|
|
||||||
throw Exception('Failed to compress bitmap.');
|
|
||||||
}
|
|
||||||
|
|
||||||
final outputJBytes = outputStream.toByteArray();
|
|
||||||
if (outputJBytes == null) {
|
|
||||||
throw Exception('Failed to get byte array from output stream.');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final outputList = outputJBytes.toList();
|
|
||||||
return Uint8List.fromList(outputList);
|
|
||||||
} finally {
|
|
||||||
outputJBytes.release();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
outputStream.release();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
compressFormat.release();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
bitmap.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputJBytes = outputStream.toByteArray();
|
||||||
|
if (outputJBytes == null) {
|
||||||
|
throw Exception('Failed to get byte array from output stream.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uint8List.fromList(outputJBytes.toList());
|
||||||
} finally {
|
} finally {
|
||||||
inputJBytes.release();
|
inputJBytes?.release();
|
||||||
|
bitmap?.recycle();
|
||||||
|
bitmap?.release();
|
||||||
|
compressFormat?.release();
|
||||||
|
outputStream?.release();
|
||||||
|
outputJBytes?.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,18 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
OutputFormat format = OutputFormat.jpeg,
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
int quality = 100,
|
int quality = 100,
|
||||||
}) async {
|
}) async {
|
||||||
final inputPtr = calloc<Uint8>(inputData.length);
|
Pointer<Uint8>? inputPtr;
|
||||||
|
CFDataRef? cfData;
|
||||||
|
CGImageSourceRef? imageSource;
|
||||||
|
CGImageRef? cgImage;
|
||||||
|
CFMutableDataRef? outputData;
|
||||||
|
CGImageDestinationRef? destination;
|
||||||
|
CFDictionaryRef? properties;
|
||||||
try {
|
try {
|
||||||
|
inputPtr = calloc<Uint8>(inputData.length);
|
||||||
inputPtr.asTypedList(inputData.length).setAll(0, inputData);
|
inputPtr.asTypedList(inputData.length).setAll(0, inputData);
|
||||||
|
|
||||||
final cfData = CFDataCreate(
|
cfData = CFDataCreate(
|
||||||
kCFAllocatorDefault,
|
kCFAllocatorDefault,
|
||||||
inputPtr.cast(),
|
inputPtr.cast(),
|
||||||
inputData.length,
|
inputData.length,
|
||||||
|
|
@ -48,17 +55,17 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
throw Exception('Failed to create CFData from input data.');
|
throw Exception('Failed to create CFData from input data.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final imageSource = CGImageSourceCreateWithData(cfData, nullptr);
|
imageSource = CGImageSourceCreateWithData(cfData, nullptr);
|
||||||
if (imageSource == nullptr) {
|
if (imageSource == nullptr) {
|
||||||
throw Exception('Failed to create CGImageSource. Invalid image data.');
|
throw Exception('Failed to create CGImageSource. Invalid image data.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
|
cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
|
||||||
if (cgImage == nullptr) {
|
if (cgImage == nullptr) {
|
||||||
throw Exception('Failed to decode image.');
|
throw Exception('Failed to decode image.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final outputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
outputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
||||||
if (outputData == nullptr) {
|
if (outputData == nullptr) {
|
||||||
throw Exception('Failed to create output CFData.');
|
throw Exception('Failed to create output CFData.');
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +88,7 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
.retainAndAutorelease()
|
.retainAndAutorelease()
|
||||||
.cast<CFString>();
|
.cast<CFString>();
|
||||||
|
|
||||||
final destination = CGImageDestinationCreateWithData(
|
destination = CGImageDestinationCreateWithData(
|
||||||
outputData,
|
outputData,
|
||||||
cfString,
|
cfString,
|
||||||
1,
|
1,
|
||||||
|
|
@ -91,7 +98,8 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
throw Exception('Failed to create CGImageDestination.');
|
throw Exception('Failed to create CGImageDestination.');
|
||||||
}
|
}
|
||||||
|
|
||||||
CGImageDestinationAddImage(destination, cgImage, nullptr);
|
properties = _createPropertiesForFormat(format, quality);
|
||||||
|
CGImageDestinationAddImage(destination, cgImage, properties ?? nullptr);
|
||||||
|
|
||||||
final success = CGImageDestinationFinalize(destination);
|
final success = CGImageDestinationFinalize(destination);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -106,7 +114,45 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
|
|
||||||
return Uint8List.fromList(bytePtr.cast<Uint8>().asTypedList(length));
|
return Uint8List.fromList(bytePtr.cast<Uint8>().asTypedList(length));
|
||||||
} finally {
|
} 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
/// |--------|-----------|---------|
|
/// |--------|-----------|---------|
|
||||||
/// | jpeg | ✓ | ✓ |
|
/// | jpeg | ✓ | ✓ |
|
||||||
/// | png | ✓ | ✓ |
|
/// | png | ✓ | ✓ |
|
||||||
/// | webp | ✓ | ✓ |
|
/// | webp | | ✓ |
|
||||||
|
/// | heic | ✓ | |
|
||||||
///
|
///
|
||||||
/// **Notes:**
|
/// **Notes:**
|
||||||
/// - [jpeg]: Good compression with adjustable quality
|
/// - [jpeg]: Good compression with adjustable quality
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue