diff --git a/JxlCoder.podspec b/JxlCoder.podspec index 885a92c..7ad6723 100644 --- a/JxlCoder.podspec +++ b/JxlCoder.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'JxlCoder' - s.version = '1.3.6' + s.version = '1.4.0' s.summary = 'JXL coder for iOS and MacOS' s.description = 'Provides support for JXL files in iOS and MacOS' s.homepage = 'https://github.com/awxkee/jxl-coder-swift' diff --git a/Sources/JxlCoder/JXLAnimatedEncoder.swift b/Sources/JxlCoder/JXLAnimatedEncoder.swift index c9cb719..4b6482d 100644 --- a/Sources/JxlCoder/JXLAnimatedEncoder.swift +++ b/Sources/JxlCoder/JXLAnimatedEncoder.swift @@ -29,23 +29,24 @@ import jxlc #endif public class JXLAnimatedEncoder { - + private let enc: CJpegXLAnimatedEncoder - + public init(width: Int, height: Int, numLoops: Int = 0, // 0 - means infinity colorSpace: JXLColorSpace = .rgba, compressionOption: JXLCompressionOption = .lossy, - effort: Int = 4, quality: Int = 0) throws { + effort: Int = 4, quality: Int = 0, decodingSpeed: JXLEncoderDecodingSpeed = .slowest) throws { enc = try CJpegXLAnimatedEncoder(Int32(width), height: Int32(height), numLoops: Int32(numLoops), colorSpace: colorSpace, compressionOption: compressionOption, effort: Int32(effort), - quality: Int32(quality)) + quality: Int32(quality), + decodingSpeed: decodingSpeed) } - + /** - Parameter frame: all the frames must match provided width and height in constructor - Parameter duration: length of the frame in milliseconds @@ -53,7 +54,7 @@ public class JXLAnimatedEncoder { public func add(frame: JXLPlatformImage, duration ms: Int) throws { try enc.addFrame(frame, duration: Int32(ms)) } - + public func finish() throws -> Data { try enc.finish() } diff --git a/Sources/JxlCoder/JXLCoder.swift b/Sources/JxlCoder/JXLCoder.swift index 4995b21..ee3c38f 100644 --- a/Sources/JxlCoder/JXLCoder.swift +++ b/Sources/JxlCoder/JXLCoder.swift @@ -102,11 +102,13 @@ public class JXLCoder { colorSpace: JXLColorSpace = .rgb, compressionOption: JXLCompressionOption = .lossy, effort: Int = 7, - quality: Int = 0) throws -> Data { + quality: Int = 0, + decodingSpeed: JXLEncoderDecodingSpeed = .slowest) throws -> Data { return try shared.encode(image, colorSpace: colorSpace, compressionOption: compressionOption, effort: Int32(effort), - quality: Int32(quality)) + quality: Int32(quality), + decodingSpeed: decodingSpeed) } /*** diff --git a/Sources/jxlc/CJpegXLAnimatedEncoder.h b/Sources/jxlc/CJpegXLAnimatedEncoder.h index 1be8ff1..ec221e5 100644 --- a/Sources/jxlc/CJpegXLAnimatedEncoder.h +++ b/Sources/jxlc/CJpegXLAnimatedEncoder.h @@ -31,9 +31,11 @@ @interface CJpegXLAnimatedEncoder : NSObject -(nullable id)initWith:(int)width height:(int)height numLoops:(int)numLoops colorSpace:(JXLColorSpace)colorSpace - compressionOption:(JXLCompressionOption)compressionOption - effort:(int)effort - quality:(int)quality error:(NSError * _Nullable *_Nullable)error; + compressionOption:(JXLCompressionOption)compressionOption + effort:(int)effort + quality:(int)quality + decodingSpeed:(JXLEncoderDecodingSpeed)decodingSpeed + error:(NSError * _Nullable *_Nullable)error; -(nullable void*)addFrame:(nonnull JXLSystemImage *)platformImage duration:(int)duration error:(NSError * _Nullable *_Nullable)error; -(nullable NSData*)finish:(NSError * _Nullable *_Nullable)error; @end diff --git a/Sources/jxlc/CJpegXLAnimatedEncoder.mm b/Sources/jxlc/CJpegXLAnimatedEncoder.mm index 5e1dd3a..fabdab7 100644 --- a/Sources/jxlc/CJpegXLAnimatedEncoder.mm +++ b/Sources/jxlc/CJpegXLAnimatedEncoder.mm @@ -50,7 +50,9 @@ -(nullable id)initWith:(int)width height:(int)height colorSpace:(JXLColorSpace)colorSpace compressionOption:(JXLCompressionOption)compressionOption effort:(int)effort - quality:(int)quality error:(NSError * _Nullable *_Nullable)error { + quality:(int)quality + decodingSpeed:(JXLEncoderDecodingSpeed)decodingSpeed + error:(NSError * _Nullable *_Nullable)error { JxlPixelType jColorspace; JxlCompressionOption jCompressionOption; @@ -73,7 +75,7 @@ -(nullable id)initWith:(int)width height:(int)height } try { - enc = new JxlAnimatedEncoder(width, height, jColorspace, er8, jCompressionOption, numLoops, quality, effort); + enc = new JxlAnimatedEncoder(width, height, jColorspace, er8, jCompressionOption, numLoops, quality, effort, (int)decodingSpeed); } catch (AnimatedEncoderError& err) { NSString *str = [[NSString alloc] initWithCString:err.what() encoding:NSUTF8StringEncoding]; *error = [[NSError alloc] initWithDomain:@"JXLCoder" code:500 userInfo:@{ NSLocalizedDescriptionKey: str }]; diff --git a/Sources/jxlc/JXLSystemImage.hpp b/Sources/jxlc/JXLSystemImage.hpp index 2fcf34d..1c3d810 100644 --- a/Sources/jxlc/JXLSystemImage.hpp +++ b/Sources/jxlc/JXLSystemImage.hpp @@ -67,6 +67,14 @@ typedef NS_ENUM(NSInteger, JxlSampler) { kHann NS_SWIFT_NAME(hann) }; +typedef NS_ENUM(NSInteger, JXLEncoderDecodingSpeed) { + kSlowest NS_SWIFT_NAME(slowest) = 0, + kSlow NS_SWIFT_NAME(slow) = 1, + kMedium NS_SWIFT_NAME(medium) = 2, + kFast NS_SWIFT_NAME(fast) = 3, + kFastest NS_SWIFT_NAME(fastest) = 4 +}; + @interface JXLSystemImage (JXLColorData) - (nullable uint8_t*)jxlRGBAPixels:(nonnull size_t*)bufferSize width:(nonnull int*)xSize height:(nonnull int*)ySize; @end diff --git a/Sources/jxlc/JxlAnimatedEncoder.hpp b/Sources/jxlc/JxlAnimatedEncoder.hpp index 8aa6470..6b4d381 100644 --- a/Sources/jxlc/JxlAnimatedEncoder.hpp +++ b/Sources/jxlc/JxlAnimatedEncoder.hpp @@ -54,7 +54,7 @@ class JxlAnimatedEncoder { JxlAnimatedEncoder(int width, int height, JxlPixelType pixelType, JxlEncodingPixelFormat encodingPixelFormat, JxlCompressionOption compressionOption, - int numLoops, int quality, int effort): width(width), height(height), + int numLoops, int quality, int effort, int decodingSpeed): width(width), height(height), pixelType(pixelType), encodingPixelFormat(encodingPixelFormat), compressionOption(compressionOption), quality(quality), effort(effort) { if (!enc || !runner) { @@ -166,6 +166,12 @@ class JxlAnimatedEncoder { throw AnimatedEncoderError(str); } + if (JXL_ENC_SUCCESS != + JxlEncoderFrameSettingsSetOption(frameSettings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, decodingSpeed)) { + std::string str = "Set decoding speed has failed"; + throw AnimatedEncoderError(str); + } + if (pixelType == rgba) { if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(frameSettings, 0, JXLGetDistance(quality))) { diff --git a/Sources/jxlc/JxlInternalCoder.h b/Sources/jxlc/JxlInternalCoder.h index 94efe1b..05511a6 100644 --- a/Sources/jxlc/JxlInternalCoder.h +++ b/Sources/jxlc/JxlInternalCoder.h @@ -43,7 +43,9 @@ colorSpace:(JXLColorSpace)colorSpace compressionOption:(JXLCompressionOption)compressionOption effort:(int)effort - quality:(int)quality error:(NSError * _Nullable *_Nullable)error; + quality:(int)quality + decodingSpeed:(JXLEncoderDecodingSpeed)decodingSpeed + error:(NSError * _Nullable *_Nullable)error; @end #endif /* JXLCoder_h */ diff --git a/Sources/jxlc/JxlInternalCoder.mm b/Sources/jxlc/JxlInternalCoder.mm index d601251..6573ade 100644 --- a/Sources/jxlc/JxlInternalCoder.mm +++ b/Sources/jxlc/JxlInternalCoder.mm @@ -47,19 +47,21 @@ static inline float JXLGetDistance(const int quality) if (quality == 0) return(1.0f); float distance = quality >= 100 ? 0.0 - : quality >= 30 - ? 0.1 + (100 - quality) * 0.09 - : 53.0 / 3000.0 * quality * quality - - 23.0 / 20.0 * quality + 25.0; + : quality >= 30 + ? 0.1 + (100 - quality) * 0.09 + : 53.0 / 3000.0 * quality * quality - + 23.0 / 20.0 * quality + 25.0; return distance; } @implementation JxlInternalCoder - (nullable NSData *)encode:(nonnull JXLSystemImage *)platformImage - colorSpace:(JXLColorSpace)colorSpace - compressionOption:(JXLCompressionOption)compressionOption - effort:(int)effort - quality:(int)quality error:(NSError * _Nullable *_Nullable)error { + colorSpace:(JXLColorSpace)colorSpace + compressionOption:(JXLCompressionOption)compressionOption + effort:(int)effort + quality:(int)quality + decodingSpeed:(JXLEncoderDecodingSpeed)decodingSpeed + error:(NSError * _Nullable *_Nullable)error { try { if (quality < 0 || quality > 100) { *error = [[NSError alloc] initWithDomain:@"JXLCoder" code:500 userInfo:@{ NSLocalizedDescriptionKey: @"Quality must be clamped in 0...100" }]; @@ -117,7 +119,9 @@ - (nullable NSData *)encode:(nonnull JXLSystemImage *)platformImage } JXLDataWrapper* wrapper = new JXLDataWrapper(); - auto encoded = EncodeJxlOneshot(pixels, width, height, &wrapper->data, jColorspace, jCompressionOption, JXLGetDistance(quality), effort); + auto encoded = EncodeJxlOneshot(pixels, width, height, &wrapper->data, + jColorspace, jCompressionOption, JXLGetDistance(quality), + effort, (int)decodingSpeed); if (!encoded) { delete wrapper; *error = [[NSError alloc] initWithDomain:@"JXLCoder" code:500 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot encode JXL image" }]; @@ -135,8 +139,8 @@ - (nullable NSData *)encode:(nonnull JXLSystemImage *)platformImage return data; } catch (std::bad_alloc &err) { *error = [[NSError alloc] initWithDomain:@"JXLCoder" - code:500 - userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Encoding image memory error: %s", err.what()] }]; + code:500 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Encoding image memory error: %s", err.what()] }]; return nullptr; } } @@ -191,11 +195,11 @@ - (CGSize)getSize:(nonnull NSInputStream *)inputStream error:(NSError *_Nullable } - (nullable JXLSystemImage *)decode:(nonnull NSInputStream *)inputStream - rescale:(CGSize)rescale - pixelFormat:(JXLPreferredPixelFormat)preferredPixelFormat - sampler:(JxlSampler)sampler - scale:(int)scale - error:(NSError *_Nullable * _Nullable)error { + rescale:(CGSize)rescale + pixelFormat:(JXLPreferredPixelFormat)preferredPixelFormat + sampler:(JxlSampler)sampler + scale:(int)scale + error:(NSError *_Nullable * _Nullable)error { try { int buffer_length = 30196; std::vector buffer; @@ -311,8 +315,8 @@ - (nullable JXLSystemImage *)decode:(nonnull NSInputStream *)inputStream } auto scaleResult = [RgbaScaler scaleData:outputData width:(int)xSize height:(int)ySize - newWidth:(int)rescale.width newHeight:(int)rescale.height - components:components pixelFormat:useFloats ? kF16 : kU8 sampler:xSampler]; + newWidth:(int)rescale.width newHeight:(int)rescale.height + components:components pixelFormat:useFloats ? kF16 : kU8 sampler:xSampler]; if (!scaleResult) { *error = [[NSError alloc] initWithDomain:@"JXLCoder" code:500 userInfo:@{ NSLocalizedDescriptionKey: @"Rescale image has failed" }]; return nil; @@ -382,17 +386,17 @@ - (nullable JXLSystemImage *)decode:(nonnull NSInputStream *)inputStream return NULL; } JXLSystemImage *image = nil; - #if JXL_PLUGIN_MAC +#if JXL_PLUGIN_MAC image = [[NSImage alloc] initWithCGImage:imageRef size:CGSizeZero]; - #else +#else image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; - #endif +#endif return image; } catch (std::bad_alloc &err) { - *error = [[NSError alloc] initWithDomain:@"JXLCoder" - code:500 - userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Decoding image memory error: %s", err.what()] }]; + *error = [[NSError alloc] initWithDomain:@"JXLCoder" + code:500 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Decoding image memory error: %s", err.what()] }]; return nullptr; } } diff --git a/Sources/jxlc/JxlWorker.cpp b/Sources/jxlc/JxlWorker.cpp index 5c52924..4424f60 100644 --- a/Sources/jxlc/JxlWorker.cpp +++ b/Sources/jxlc/JxlWorker.cpp @@ -232,8 +232,11 @@ bool DecodeBasicInfo(const uint8_t *jxl, size_t size, size_t *xsize, size_t *ysi */ bool EncodeJxlOneshot(const std::vector &pixels, const uint32_t xsize, const uint32_t ysize, std::vector *compressed, - JxlPixelType colorspace, JxlCompressionOption compressionOption, - float compressionDistance, int effort) { + JxlPixelType colorspace, + JxlCompressionOption compressionOption, + float compressionDistance, + int effort, + int decodingSpeed) { auto enc = JxlEncoderMake(nullptr); auto runner = JxlThreadParallelRunnerMake(nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads()); @@ -308,6 +311,11 @@ bool EncodeJxlOneshot(const std::vector &pixels, const uint32_t xsize, return false; } + if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption(frameSettings, + JXL_ENC_FRAME_SETTING_DECODING_SPEED, decodingSpeed)) { + return false; + } + if (JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frameSettings, compressionDistance)) { return false; diff --git a/Sources/jxlc/JxlWorker.hpp b/Sources/jxlc/JxlWorker.hpp index 53a3895..bda87c3 100644 --- a/Sources/jxlc/JxlWorker.hpp +++ b/Sources/jxlc/JxlWorker.hpp @@ -46,8 +46,11 @@ bool DecodeJpegXlOneShot(const uint8_t *jxl, size_t size, bool DecodeBasicInfo(const uint8_t *jxl, size_t size, size_t *xsize, size_t *ysize); bool EncodeJxlOneshot(const std::vector &pixels, const uint32_t xsize, const uint32_t ysize, std::vector *compressed, - JxlPixelType colorspace, JxlCompressionOption compression_option, - float compression_distance, int effort); + JxlPixelType colorspace, + JxlCompressionOption compressionOption, + float compressionDistance, + int effort, + int decodingSpeed); bool isJXL(std::vector& src);