Add jpeg-xl

This commit is contained in:
Ali 2023-08-22 23:44:57 +04:00
parent 21afe24790
commit a35f38c0f6
1593 changed files with 482169 additions and 386 deletions

View File

@ -44,6 +44,14 @@ public func compressImageToJPEG(_ image: UIImage, quality: Float) -> Data? {
return data as Data
}
public func compressImageToJPEGXL(_ image: UIImage, quality: Int) -> Data? {
return compressJPEGXLData(image, Int32(quality))
}
public func decompressImageFromJPEGXL(data: Data) -> UIImage? {
return decompressJPEGXLData(data)
}
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
public func compressImage(_ image: UIImage, quality: Float) -> Data? {
let data = NSMutableData()

View File

@ -495,50 +495,128 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
case let .asset(asset):
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
let scaledSize = size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0))
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
attributes.append(MediaSpoilerMessageAttribute())
}
let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString()))
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:]
text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
if let file = value.file {
if let packId = value.interactivelySelectedFromPackId {
bubbleUpEmojiOrStickersetsById[file.fileId.id] = packId
if context.sharedContext.immediateExperimentalUISettings.storiesJpegExperiment {
let sizes: [Int32] = [2048, 1280]
let formats: [MediaImageFormat] = [.jxl, .jpeg]
let qualities: [Int32: [Int32]] = [
MediaImageFormat.jxl.rawValue: [
50,
75
],
MediaImageFormat.jpeg.rawValue: [
75
]
]
for sizeSide in sizes {
for format in formats {
for quality in qualities[format.rawValue]! {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let resource = PhotoLibraryMediaResource(
localIdentifier: asset.localIdentifier,
uniqueId: Int64.random(in: Int64.min ... Int64.max),
width: sizeSide,
height: sizeSide,
format: format,
quality: quality
)
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
let scaledSize = size.aspectFittedOrSmaller(CGSize(width: CGFloat(sizeSide), height: CGFloat(sizeSide)))
let media: Media
media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: format == .jxl ? "image/jxl" : "image/jpeg", size: nil, attributes: [
.FileName(fileName: format == .jxl ? "image\(sizeSide)-q\(quality).jxl" : "image\(sizeSide)-q\(quality).jpg"),
.ImageSize(size: PixelDimensions(scaledSize))
])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
attributes.append(MediaSpoilerMessageAttribute())
}
let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString()))
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:]
text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
if let file = value.file {
if let packId = value.interactivelySelectedFromPackId {
bubbleUpEmojiOrStickersetsById[file.fileId.id] = packId
}
}
}
})
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
for entity in entities {
if case let .CustomEmoji(_, fileId) = entity.type {
if let packId = bubbleUpEmojiOrStickersetsById[fileId] {
if !bubbleUpEmojiOrStickersets.contains(packId) {
bubbleUpEmojiOrStickersets.append(packId)
}
}
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
}
}
}
})
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
for entity in entities {
if case let .CustomEmoji(_, fileId) = entity.type {
if let packId = bubbleUpEmojiOrStickersetsById[fileId] {
if !bubbleUpEmojiOrStickersets.contains(packId) {
bubbleUpEmojiOrStickersets.append(packId)
} else {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
let scaledSize = size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0))
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max))
let media: Media
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
attributes.append(MediaSpoilerMessageAttribute())
}
let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString()))
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:]
text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
if let file = value.file {
if let packId = value.interactivelySelectedFromPackId {
bubbleUpEmojiOrStickersetsById[file.fileId.id] = packId
}
}
}
})
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
for entity in entities {
if case let .CustomEmoji(_, fileId) = entity.type {
if let packId = bubbleUpEmojiOrStickersetsById[fileId] {
if !bubbleUpEmojiOrStickersets.contains(packId) {
bubbleUpEmojiOrStickersets.append(packId)
}
}
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
case .tempFile:
break
}

View File

@ -84,7 +84,7 @@ extension UIImage.Orientation {
private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2)
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, height: Int32?, format: MediaImageFormat?, quality: Int32?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
return Signal { subscriber in
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
@ -97,7 +97,12 @@ public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaRe
option.isNetworkAccessAllowed = true
option.isSynchronous = false
let size = CGSize(width: 1280.0, height: 1280.0)
let size: CGSize
if let width, let height {
size = CGSize(width: CGFloat(width), height: CGFloat(height))
} else {
size = CGSize(width: 1280.0, height: 1280.0)
}
queue.addTask(ThreadPoolTask({ _ in
let startTime = CACurrentMediaTime()
@ -127,14 +132,27 @@ public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaRe
print("scaled completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
#endif
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
#if DEBUG
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
#endif
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
subscriber.putCompletion()
} else {
subscriber.putCompletion()
switch format {
case .none, .jpeg:
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
#if DEBUG
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
#endif
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
subscriber.putCompletion()
} else {
subscriber.putCompletion()
}
case .jxl:
if let scaledImage = scaledImage, let data = compressImageToJPEGXL(scaledImage, quality: Int(quality ?? 75)) {
#if DEBUG
print("jpegxl compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
#endif
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
subscriber.putCompletion()
} else {
subscriber.putCompletion()
}
}
semaphore.signal()
}

View File

@ -220,6 +220,11 @@ public struct PhotoLibraryMediaResourceId {
}
}
public enum MediaImageFormat: Int32 {
case jpeg
case jxl
}
public class PhotoLibraryMediaResource: TelegramMediaResource {
public var size: Int64? {
return nil
@ -227,20 +232,52 @@ public class PhotoLibraryMediaResource: TelegramMediaResource {
public let localIdentifier: String
public let uniqueId: Int64
public let width: Int32?
public let height: Int32?
public let format: MediaImageFormat?
public let quality: Int32?
public init(localIdentifier: String, uniqueId: Int64) {
public init(localIdentifier: String, uniqueId: Int64, width: Int32? = nil, height: Int32? = nil, format: MediaImageFormat? = nil, quality: Int32? = nil) {
self.localIdentifier = localIdentifier
self.uniqueId = uniqueId
self.width = width
self.height = height
self.format = format
self.quality = quality
}
public required init(decoder: PostboxDecoder) {
self.localIdentifier = decoder.decodeStringForKey("i", orElse: "")
self.uniqueId = decoder.decodeInt64ForKey("uid", orElse: 0)
self.width = decoder.decodeOptionalInt32ForKey("w")
self.height = decoder.decodeOptionalInt32ForKey("h")
self.format = decoder.decodeOptionalInt32ForKey("f").flatMap(MediaImageFormat.init(rawValue:))
self.quality = decoder.decodeOptionalInt32ForKey("q")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.localIdentifier, forKey: "i")
encoder.encodeInt64(self.uniqueId, forKey: "uid")
if let width = self.width {
encoder.encodeInt32(width, forKey: "w")
} else {
encoder.encodeNil(forKey: "w")
}
if let height = self.height {
encoder.encodeInt32(height, forKey: "h")
} else {
encoder.encodeNil(forKey: "h")
}
if let format = self.format {
encoder.encodeInt32(format.rawValue, forKey: "f")
} else {
encoder.encodeNil(forKey: "f")
}
if let quality = self.quality {
encoder.encodeInt32(quality, forKey: "q")
} else {
encoder.encodeNil(forKey: "q")
}
}
public var id: MediaResourceId {
@ -249,7 +286,25 @@ public class PhotoLibraryMediaResource: TelegramMediaResource {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? PhotoLibraryMediaResource {
return self.localIdentifier == to.localIdentifier && self.uniqueId == to.uniqueId
if self.localIdentifier != to.localIdentifier {
return false
}
if self.uniqueId != to.uniqueId {
return false
}
if self.width != to.width {
return false
}
if self.height != to.height {
return false
}
if self.format != to.format {
return false
}
if self.quality != to.quality {
return false
}
return true
} else {
return false
}

View File

@ -5,6 +5,7 @@ objc_library(
enable_modules = True,
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
]),
hdrs = glob([
@ -15,6 +16,7 @@ objc_library(
],
deps = [
"//third-party/mozjpeg:mozjpeg",
"//third-party/libjxl:jxl",
],
visibility = [
"//visibility:public",

View File

@ -1,6 +1,17 @@
#import <UIKit/UIKit.h>
#ifdef __cplusplus
extern "C" {
#endif
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage);
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data);
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size);
UIImage * _Nullable decompressImage(NSData * _Nonnull sourceData);
NSData * _Nullable compressJPEGXLData(UIImage * _Nonnull sourceImage, int quality);
UIImage * _Nullable decompressJPEGXLData(NSData * _Nonnull data);
#ifdef __cplusplus
}
#endif

View File

@ -1,335 +0,0 @@
#import <MozjpegBinding/MozjpegBinding.h>
#import <mozjpeg/turbojpeg.h>
#import <mozjpeg/jpeglib.h>
#import <Accelerate/Accelerate.h>
static NSData *getHeaderPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=" options:0];
});
return value;
}
static NSData *getFooterPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9k=" options:0];
});
return value;
}
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data) {
NSMutableArray<NSNumber *> *result = [[NSMutableArray alloc] init];
const uint8_t *dataBytes = data.bytes;
int offset = 0;
while (offset < data.length) {
bool found = false;
for (int i = offset + 2; i < data.length - 1; i++) {
if (dataBytes[i] == 0xffU && dataBytes[i + 1] == 0xdaU) {
if (offset != 0) {
[result addObject:@(i)];
}
offset = i;
found = true;
}
}
if (!found) {
break;
}
}
#if DEBUG
static NSString *sessionPrefix = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sessionPrefix = [NSString stringWithFormat:@"%u", arc4random()];
});
NSString *randomId = [NSString stringWithFormat:@"%u", arc4random()];
NSString *dirPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:sessionPrefix] stringByAppendingPathComponent:randomId];
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:true attributes:nil error:nil];
for (int i = 0; i < result.count + 1; i++) {
NSString *filePath = [dirPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.jpg", i]];
if (i == result.count) {
[data writeToFile:filePath atomically:true];
} else {
[[data subdataWithRange:NSMakeRange(0, [result[i] intValue])] writeToFile:filePath atomically:true];
}
}
NSLog(@"Path: %@", dirPath);
#endif
return result;
}
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
int width = (int)(sourceImage.size.width * sourceImage.scale);
int height = (int)(sourceImage.size.height * sourceImage.scale);
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = TRUE;
jpeg_set_quality(&cinfo, 72, 1);
jpeg_simple_progression(&cinfo);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSData *result = [[NSData alloc] initWithBytes:outBuffer length:outSize];
jpeg_destroy_compress(&cinfo);
free(buffer);
return result;
}
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size) {
CGSize fittedSize = image.size;
if (fittedSize.width > size.width) {
fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f))));
}
if (fittedSize.height > size.height) {
fittedSize = CGSizeMake((int)((fittedSize.width * size.height / MAX(fittedSize.height, 1.0f))), size.height);
}
int width = (int)fittedSize.width;
int height = (int)fittedSize.height;
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), image.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = FALSE;
jpeg_set_quality(&cinfo, 20, 1);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSMutableData *serializedData = nil;
NSData *headerPattern = getHeaderPattern();
NSData *footerPattern = getFooterPattern();
if (outBuffer[164] == height && outBuffer[166] == width && headerPattern != nil && footerPattern != nil) {
outBuffer[164] = 0;
outBuffer[166] = 0;
if (memcmp(headerPattern.bytes, outBuffer, headerPattern.length) == 0) {
if (memcmp(footerPattern.bytes, outBuffer + outSize - footerPattern.length, footerPattern.length) == 0) {
serializedData = [[NSMutableData alloc] init];
uint8_t version = 1;
[serializedData appendBytes:&version length:1];
uint8_t outWidth = (uint8_t)width;
uint8_t outHeight = (uint8_t)height;
[serializedData appendBytes:&outHeight length:1];
[serializedData appendBytes:&outWidth length:1];
unsigned long contentSize = outSize - headerPattern.length - footerPattern.length;
[serializedData appendBytes:outBuffer + headerPattern.length length:contentSize];
}
}
}
jpeg_destroy_compress(&cinfo);
free(buffer);
return serializedData;
}
UIImage * _Nullable decompressImage(NSData * _Nonnull sourceData) {
long unsigned int jpegSize = sourceData.length;
unsigned char *_compressedImage = (unsigned char *)sourceData.bytes;
int jpegSubsamp, width, height;
tjhandle _jpegDecompressor = tjInitDecompress();
if (tjDecompressHeader2(_jpegDecompressor, _compressedImage, jpegSize, &width, &height, &jpegSubsamp) != 0) {
return nil;
}
int sourceBytesPerRow = (3 * width + 31) & ~0x1F;
int targetBytesPerRow = (4 * width + 31) & ~0x1F;
unsigned char *buffer = malloc(sourceBytesPerRow * height);
tjDecompress2(_jpegDecompressor, _compressedImage, jpegSize, buffer, width, sourceBytesPerRow, height, TJPF_RGB, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE);
tjDestroy(_jpegDecompressor);
vImage_Buffer source;
source.width = width;
source.height = height;
source.rowBytes = sourceBytesPerRow;
source.data = buffer;
vImage_Buffer target;
target.width = width;
target.height = height;
target.rowBytes = targetBytesPerRow;
unsigned char *targetBuffer = malloc(targetBytesPerRow * height);
target.data = targetBuffer;
vImageConvert_RGB888toARGB8888(&source, nil, 0xff, &target, false, kvImageDoNotTile);
free(buffer);
vImage_Buffer permuteTarget;
permuteTarget.width = width;
permuteTarget.height = height;
permuteTarget.rowBytes = targetBytesPerRow;
unsigned char *permuteTargetBuffer = malloc(targetBytesPerRow * height);
permuteTarget.data = permuteTargetBuffer;
const uint8_t permuteMap[4] = {3,2,1,0};
vImagePermuteChannels_ARGB8888(&target, &permuteTarget, permuteMap, kvImageDoNotTile);
free(targetBuffer);
NSData *resultData = [[NSData alloc] initWithBytesNoCopy:permuteTargetBuffer length:targetBytesPerRow * height deallocator:^(void * _Nonnull bytes, __unused NSUInteger length) {
free(bytes);
}];
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)resultData);
static CGColorSpaceRef imageColorSpace;
static CGBitmapInfo bitmapInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
bitmapInfo = CGImageGetBitmapInfo(refImage.CGImage);
UIGraphicsEndImageContext();
});
CGImageRef cgImg = CGImageCreate(width, height, 8, 32, targetBytesPerRow, imageColorSpace, bitmapInfo, dataProvider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
UIImage *resultImage = [[UIImage alloc] initWithCGImage:cgImg];
CGImageRelease(cgImg);
return resultImage;
}

View File

@ -0,0 +1,780 @@
#import <MozjpegBinding/MozjpegBinding.h>
#define USE_JPEGLI false
#import <mozjpeg/turbojpeg.h>
#import <mozjpeg/jpeglib.h>
#import <Accelerate/Accelerate.h>
#include <jxl/encode.h>
#include <jxl/encode_cxx.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
//#include <jxl/thread_parallel_runner.h>
//#include <jxl/thread_parallel_runner_cxx.h>
#include <limits.h>
#include <string.h>
#include <sstream>
#include <string>
#include <vector>
static inline float JXLGetDistance(int32_t quality) {
if (quality == 0) {
return 1.0f;
} else if (quality >= 30) {
return 0.1f + (float)(100 - MIN(100, quality)) * 0.09f;
} else {
return 6.24f + (float)pow(2.5f, (30.0 - quality) / 5.0) / 6.25f;
}
}
NSData * _Nullable compressJPEGXLData(UIImage * _Nonnull sourceImage, int quality) {
int width = (int)(sourceImage.size.width * sourceImage.scale);
int height = (int)(sourceImage.size.height * sourceImage.scale);
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = (uint8_t *)malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
int bufferSize = bufferBytesPerRow * height;
uint8_t *buffer = (uint8_t *)malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
auto enc = JxlEncoderMake(nullptr);
JxlPixelFormat pixel_format = {3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 16};
JxlBasicInfo basic_info;
JxlEncoderInitBasicInfo(&basic_info);
basic_info.xsize = width;
basic_info.ysize = height;
basic_info.bits_per_sample = 32;
basic_info.exponent_bits_per_sample = 8;
basic_info.uses_original_profile = JXL_FALSE;
if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) {
free(buffer);
return nil;
}
JxlColorEncoding color_encoding = {};
JxlColorEncodingSetToSRGB(&color_encoding,
/*is_gray=*/pixel_format.num_channels < 3);
if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) {
free(buffer);
return nil;
}
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
JxlEncoderSetFrameDistance(frame_settings, JXLGetDistance(quality));
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8);
if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frame_settings, &pixel_format, buffer, bufferSize)) {
free(buffer);
return nil;
}
JxlEncoderCloseInput(enc.get());
NSMutableData *result = [[NSMutableData alloc] initWithLength:64];
uint8_t *next_out = (uint8_t *)result.mutableBytes;
size_t avail_out = result.length - (next_out - ((uint8_t *)result.mutableBytes));
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
size_t offset = next_out - ((uint8_t *)result.mutableBytes);
[result setLength:result.length * 2];
next_out = ((uint8_t *)result.mutableBytes) + offset;
avail_out = result.length - offset;
}
}
[result setLength:next_out - ((uint8_t *)result.mutableBytes)];
if (JXL_ENC_SUCCESS != process_result) {
free(buffer);
return nil;
}
free(buffer);
return result;
/*auto runner = JxlThreadParallelRunnerMake(
nullptr,
8);
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(),
JxlThreadParallelRunner,
runner.get())) {
fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
return false;
}*/
}
UIImage * _Nullable decompressJPEGXLData(NSData * _Nonnull data) {
//const uint8_t* jxl, size_t size, std::vector<float>* pixels, size_t* xsize, size_t* ysize, std::vector<uint8_t>* icc_profile
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE)) {
return nil;
}
/*if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) {
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
return false;
}*/
JxlBasicInfo info;
JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
JxlDecoderSetInput(dec.get(), (uint8_t const *)data.bytes, data.length);
JxlDecoderCloseInput(dec.get());
int xsize = 0;
int ysize = 0;
std::vector<uint8_t> icc_profile;
std::vector<uint8_t> pixels;
while (true) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
return nil;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
return nil;
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
return nil;
}
xsize = info.xsize;
ysize = info.ysize;
//JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
} else if (status == JXL_DEC_COLOR_ENCODING) {
// Get the ICC color profile of the pixel data
size_t icc_size;
if (JXL_DEC_SUCCESS != JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
return nil;
}
icc_profile.resize(icc_size);
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, icc_profile.data(), icc_profile.size())) {
return nil;
}
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
size_t buffer_size;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
return nil;
}
if (buffer_size != xsize * ysize * 16) {
return nil;
}
pixels.resize(xsize * ysize * 4);
void* pixels_buffer = (void*)pixels.data();
size_t pixels_buffer_size = pixels.size() * sizeof(float);
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, pixels_buffer_size)) {
return nil;
}
} else if (status == JXL_DEC_FULL_IMAGE) {
// Nothing to do. Do not yet return. If the image is an animation, more
// full frames may be decoded. This example only keeps the last one.
} else if (status == JXL_DEC_SUCCESS) {
// All decoding successfully finished.
// It's not required to call JxlDecoderReleaseInput(dec.get()) here since
// the decoder will be destroyed.
int targetBytesPerRow = xsize * 4;
uint8_t *permuteTargetBuffer = (uint8_t *)malloc(targetBytesPerRow * ysize);
memcpy(permuteTargetBuffer, pixels.data(), pixels.size());
NSData *resultData = [[NSData alloc] initWithBytesNoCopy:permuteTargetBuffer length:targetBytesPerRow * ysize deallocator:^(void * _Nonnull bytes, __unused NSUInteger length) {
free(bytes);
}];
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)resultData);
static CGColorSpaceRef imageColorSpace;
static CGBitmapInfo bitmapInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
bitmapInfo = CGImageGetBitmapInfo(refImage.CGImage);
UIGraphicsEndImageContext();
});
CGImageRef cgImg = CGImageCreate(xsize, ysize, 8, 32, targetBytesPerRow, imageColorSpace, bitmapInfo, dataProvider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
UIImage *resultImage = [[UIImage alloc] initWithCGImage:cgImg];
CGImageRelease(cgImg);
return resultImage;
} else {
return nil;
}
}
return nil;
}
static NSData *getHeaderPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=" options:0];
});
return value;
}
static NSData *getFooterPattern() {
static NSData *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSData alloc] initWithBase64EncodedString:@"/9k=" options:0];
});
return value;
}
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data) {
NSMutableArray<NSNumber *> *result = [[NSMutableArray alloc] init];
const uint8_t *dataBytes = (const uint8_t *)data.bytes;
int offset = 0;
while (offset < data.length) {
bool found = false;
for (int i = offset + 2; i < data.length - 1; i++) {
if (dataBytes[i] == 0xffU && dataBytes[i + 1] == 0xdaU) {
if (offset != 0) {
[result addObject:@(i)];
}
offset = i;
found = true;
}
}
if (!found) {
break;
}
}
#if DEBUG
static NSString *sessionPrefix = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sessionPrefix = [NSString stringWithFormat:@"%u", arc4random()];
});
NSString *randomId = [NSString stringWithFormat:@"%u", arc4random()];
NSString *dirPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:sessionPrefix] stringByAppendingPathComponent:randomId];
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:true attributes:nil error:nil];
for (int i = 0; i < result.count + 1; i++) {
NSString *filePath = [dirPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.jpg", i]];
if (i == result.count) {
[data writeToFile:filePath atomically:true];
} else {
[[data subdataWithRange:NSMakeRange(0, [result[i] intValue])] writeToFile:filePath atomically:true];
}
}
NSLog(@"Path: %@", dirPath);
#endif
return result;
}
#if USE_JPEGLI
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
int width = (int)(sourceImage.size.width * sourceImage.scale);
int height = (int)(sourceImage.size.height * sourceImage.scale);
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
//jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = TRUE;
jpeg_set_quality(&cinfo, 72, 1);
jpeg_simple_progression(&cinfo);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSData *result = [[NSData alloc] initWithBytes:outBuffer length:outSize];
jpeg_destroy_compress(&cinfo);
free(buffer);
return result;
}
#else
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
int width = (int)(sourceImage.size.width * sourceImage.scale);
int height = (int)(sourceImage.size.height * sourceImage.scale);
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = (uint8_t *)malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = (uint8_t *)malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = TRUE;
jpeg_set_quality(&cinfo, 72, 1);
jpeg_simple_progression(&cinfo);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSData *result = [[NSData alloc] initWithBytes:outBuffer length:outSize];
jpeg_destroy_compress(&cinfo);
free(buffer);
return result;
}
#endif
#if USE_JPEGLI
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size) {
CGSize fittedSize = image.size;
if (fittedSize.width > size.width) {
fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f))));
}
if (fittedSize.height > size.height) {
fittedSize = CGSizeMake((int)((fittedSize.width * size.height / MAX(fittedSize.height, 1.0f))), size.height);
}
int width = (int)fittedSize.width;
int height = (int)fittedSize.height;
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), image.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
//jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = FALSE;
jpeg_set_quality(&cinfo, 20, 1);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSMutableData *serializedData = nil;
NSData *headerPattern = getHeaderPattern();
NSData *footerPattern = getFooterPattern();
if (outBuffer[164] == height && outBuffer[166] == width && headerPattern != nil && footerPattern != nil) {
outBuffer[164] = 0;
outBuffer[166] = 0;
if (memcmp(headerPattern.bytes, outBuffer, headerPattern.length) == 0) {
if (memcmp(footerPattern.bytes, outBuffer + outSize - footerPattern.length, footerPattern.length) == 0) {
serializedData = [[NSMutableData alloc] init];
uint8_t version = 1;
[serializedData appendBytes:&version length:1];
uint8_t outWidth = (uint8_t)width;
uint8_t outHeight = (uint8_t)height;
[serializedData appendBytes:&outHeight length:1];
[serializedData appendBytes:&outWidth length:1];
unsigned long contentSize = outSize - headerPattern.length - footerPattern.length;
[serializedData appendBytes:outBuffer + headerPattern.length length:contentSize];
}
}
}
jpeg_destroy_compress(&cinfo);
free(buffer);
return serializedData;
}
#else
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size) {
CGSize fittedSize = image.size;
if (fittedSize.width > size.width) {
fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f))));
}
if (fittedSize.height > size.height) {
fittedSize = CGSizeMake((int)((fittedSize.width * size.height / MAX(fittedSize.height, 1.0f))), size.height);
}
int width = (int)fittedSize.width;
int height = (int)fittedSize.height;
int targetBytesPerRow = ((4 * (int)width) + 31) & (~31);
uint8_t *targetMemory = (uint8_t *)malloc((int)(targetBytesPerRow * height));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(targetMemory, width, height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
UIGraphicsPushContext(targetContext);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(targetContext, CGRectMake(0, 0, width, height), image.CGImage);
UIGraphicsPopContext();
int bufferBytesPerRow = ((3 * (int)width) + 31) & (~31);
uint8_t *buffer = (uint8_t *)malloc(bufferBytesPerRow * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t *color = ((uint32_t *)&targetMemory[y * targetBytesPerRow + x * 4]);
uint32_t r = ((*color >> 16) & 0xff);
uint32_t g = ((*color >> 8) & 0xff);
uint32_t b = (*color & 0xff);
buffer[y * bufferBytesPerRow + x * 3 + 0] = r;
buffer[y * bufferBytesPerRow + x * 3 + 1] = g;
buffer[y * bufferBytesPerRow + x * 3 + 2] = b;
}
}
CGContextRelease(targetContext);
free(targetMemory);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
uint8_t *outBuffer = NULL;
unsigned long outSize = 0;
jpeg_mem_dest(&cinfo, &outBuffer, &outSize);
cinfo.image_width = (uint32_t)width;
cinfo.image_height = (uint32_t)height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_c_set_int_param(&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
jpeg_set_defaults(&cinfo);
cinfo.arith_code = FALSE;
cinfo.dct_method = JDCT_ISLOW;
cinfo.optimize_coding = FALSE;
jpeg_set_quality(&cinfo, 20, 1);
jpeg_start_compress(&cinfo, 1);
JSAMPROW rowPointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
rowPointer[0] = (JSAMPROW)(buffer + cinfo.next_scanline * bufferBytesPerRow);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
}
jpeg_finish_compress(&cinfo);
NSMutableData *serializedData = nil;
NSData *headerPattern = getHeaderPattern();
NSData *footerPattern = getFooterPattern();
if (outBuffer[164] == height && outBuffer[166] == width && headerPattern != nil && footerPattern != nil) {
outBuffer[164] = 0;
outBuffer[166] = 0;
if (memcmp(headerPattern.bytes, outBuffer, headerPattern.length) == 0) {
if (memcmp(footerPattern.bytes, outBuffer + outSize - footerPattern.length, footerPattern.length) == 0) {
serializedData = [[NSMutableData alloc] init];
uint8_t version = 1;
[serializedData appendBytes:&version length:1];
uint8_t outWidth = (uint8_t)width;
uint8_t outHeight = (uint8_t)height;
[serializedData appendBytes:&outHeight length:1];
[serializedData appendBytes:&outWidth length:1];
unsigned long contentSize = outSize - headerPattern.length - footerPattern.length;
[serializedData appendBytes:outBuffer + headerPattern.length length:contentSize];
}
}
}
jpeg_destroy_compress(&cinfo);
free(buffer);
return serializedData;
}
#endif
#if USE_JPEGLI
UIImage * _Nullable decompressImage(NSData * _Nonnull sourceData) {
return [UIImage imageWithData:sourceData];
}
#else
UIImage * _Nullable decompressImage(NSData * _Nonnull sourceData) {
long unsigned int jpegSize = sourceData.length;
unsigned char *_compressedImage = (unsigned char *)sourceData.bytes;
int jpegSubsamp, width, height;
tjhandle _jpegDecompressor = tjInitDecompress();
if (tjDecompressHeader2(_jpegDecompressor, _compressedImage, jpegSize, &width, &height, &jpegSubsamp) != 0) {
return nil;
}
int sourceBytesPerRow = (3 * width + 31) & ~0x1F;
int targetBytesPerRow = (4 * width + 31) & ~0x1F;
unsigned char *buffer = (uint8_t *)malloc(sourceBytesPerRow * height);
tjDecompress2(_jpegDecompressor, _compressedImage, jpegSize, buffer, width, sourceBytesPerRow, height, TJPF_RGB, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE);
tjDestroy(_jpegDecompressor);
vImage_Buffer source;
source.width = width;
source.height = height;
source.rowBytes = sourceBytesPerRow;
source.data = buffer;
vImage_Buffer target;
target.width = width;
target.height = height;
target.rowBytes = targetBytesPerRow;
unsigned char *targetBuffer = (uint8_t *)malloc(targetBytesPerRow * height);
target.data = targetBuffer;
vImageConvert_RGB888toARGB8888(&source, nil, 0xff, &target, false, kvImageDoNotTile);
free(buffer);
vImage_Buffer permuteTarget;
permuteTarget.width = width;
permuteTarget.height = height;
permuteTarget.rowBytes = targetBytesPerRow;
unsigned char *permuteTargetBuffer = (uint8_t *)malloc(targetBytesPerRow * height);
permuteTarget.data = permuteTargetBuffer;
const uint8_t permuteMap[4] = {3,2,1,0};
vImagePermuteChannels_ARGB8888(&target, &permuteTarget, permuteMap, kvImageDoNotTile);
free(targetBuffer);
NSData *resultData = [[NSData alloc] initWithBytesNoCopy:permuteTargetBuffer length:targetBytesPerRow * height deallocator:^(void * _Nonnull bytes, __unused NSUInteger length) {
free(bytes);
}];
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)resultData);
static CGColorSpaceRef imageColorSpace;
static CGBitmapInfo bitmapInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
bitmapInfo = CGImageGetBitmapInfo(refImage.CGImage);
UIGraphicsEndImageContext();
});
CGImageRef cgImg = CGImageCreate(width, height, 8, 32, targetBytesPerRow, imageColorSpace, bitmapInfo, dataProvider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
UIImage *resultImage = [[UIImage alloc] initWithCGImage:cgImg];
CGImageRelease(cgImg);
return resultImage;
}
#endif

View File

@ -51,6 +51,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var disableBackgroundAnimation: Bool
public var logLanguageRecognition: Bool
public var storiesExperiment: Bool
public var storiesJpegExperiment: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -79,7 +80,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
disableImageContentAnalysis: false,
disableBackgroundAnimation: false,
logLanguageRecognition: false,
storiesExperiment: false
storiesExperiment: false,
storiesJpegExperiment: false
)
}
@ -109,7 +111,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
disableImageContentAnalysis: Bool,
disableBackgroundAnimation: Bool,
logLanguageRecognition: Bool,
storiesExperiment: Bool
storiesExperiment: Bool,
storiesJpegExperiment: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -137,6 +140,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.disableBackgroundAnimation = disableBackgroundAnimation
self.logLanguageRecognition = logLanguageRecognition
self.storiesExperiment = storiesExperiment
self.storiesJpegExperiment = storiesJpegExperiment
}
public init(from decoder: Decoder) throws {
@ -168,6 +172,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.disableBackgroundAnimation = try container.decodeIfPresent(Bool.self, forKey: "disableBackgroundAnimation") ?? false
self.logLanguageRecognition = try container.decodeIfPresent(Bool.self, forKey: "logLanguageRecognition") ?? false
self.storiesExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesExperiment") ?? false
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -199,6 +204,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode(self.disableBackgroundAnimation, forKey: "disableBackgroundAnimation")
try container.encode(self.logLanguageRecognition, forKey: "logLanguageRecognition")
try container.encode(self.storiesExperiment, forKey: "storiesExperiment")
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
}
}

129
third-party/libjxl/BUILD vendored Normal file
View File

@ -0,0 +1,129 @@
headers = [
"jxl/codestream_header.h",
"jxl/cms_interface.h",
"jxl/color_encoding.h",
"jxl/decode_cxx.h",
"jxl/decode.h",
"jxl/encode_cxx.h",
"jxl/encode.h",
"jxl/jxl_export.h",
"jxl/jxl_threads_export.h",
"jxl/memory_manager.h",
"jxl/parallel_runner.h",
"jxl/stats.h",
"jxl/types.h",
"jxl/version.h",
]
libs = [
"jxl",
]
brotli_libs = [
"libbrotlicommon",
"libbrotlidec",
"libbrotlienc",
]
highway_libs = [
"libhwy"
]
filegroup(
name = "libjxl_sources",
srcs = glob([
"libjxl/**/*"
]),
)
genrule(
name = "libjxl_build",
srcs = [
"build-libjxl-bazel.sh",
":libjxl_sources",
"@cmake_tar_gz//file",
],
cmd_bash =
"""
set -ex
if [ "$(TARGET_CPU)" == "ios_armv7" ]; then
BUILD_ARCH="armv7"
elif [ "$(TARGET_CPU)" == "ios_arm64" ]; then
BUILD_ARCH="arm64"
elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then
BUILD_ARCH="sim_arm64"
elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then
BUILD_ARCH="x86_64"
else
echo "Unsupported architecture $(TARGET_CPU)"
fi
BUILD_DIR="$(RULEDIR)/build_$${BUILD_ARCH}"
rm -rf "$$BUILD_DIR"
mkdir -p "$$BUILD_DIR"
CMAKE_DIR="$$(pwd)/$$BUILD_DIR/cmake"
rm -rf "$$CMAKE_DIR"
mkdir -p "$$CMAKE_DIR"
tar -xzf "$(location @cmake_tar_gz//file)" -C "$$CMAKE_DIR"
cp $(location :build-libjxl-bazel.sh) "$$BUILD_DIR/"
SOURCE_PATH="third-party/libjxl/libjxl"
cp -R "$$SOURCE_PATH" "$$BUILD_DIR/"
mkdir -p "$$BUILD_DIR/Public/jxl"
PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" sh $$BUILD_DIR/build-libjxl-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libjxl" "$$BUILD_DIR"
""" +
"\n".join([
"cp -f \"$$BUILD_DIR/build/lib/include/{}\" \"$(location Public/{})\"".format(header, header) for header in headers
]) +
"\n" +
"\n".join([
"cp -f \"$$BUILD_DIR/build/lib/lib{}.a\" \"$(location Public/jxl/lib/lib{}.a)\"".format(lib, lib) for lib in libs
]) +
"\n" +
"\n".join([
"cp -f \"$$BUILD_DIR/build/third_party/brotli/{}.a\" \"$(location Public/jxl/lib/{}.a)\"".format(lib, lib) for lib in brotli_libs
]) +
"\n" +
"\n".join([
"cp -f \"$$BUILD_DIR/build/third_party/highway/{}.a\" \"$(location Public/jxl/lib/{}.a)\"".format(lib, lib) for lib in highway_libs
]),
outs = ["Public/" + x for x in headers] +
["Public/jxl/lib/lib{}.a".format(x) for x in libs] +
["Public/jxl/lib/{}.a".format(x) for x in brotli_libs] +
["Public/jxl/lib/{}.a".format(x) for x in highway_libs],
visibility = [
"//visibility:public",
]
)
cc_library(
name = "jxl_lib",
srcs = [":Public/jxl/lib/lib" + x + ".a" for x in libs] +
[":Public/jxl/lib/" + x + ".a" for x in brotli_libs] +
[":Public/jxl/lib/" + x + ".a" for x in highway_libs],
)
objc_library(
name = "jxl",
module_name = "jxl",
enable_modules = True,
hdrs = [":Public/" + x for x in headers],
includes = [
"Public",
"Public/jxl",
],
deps = [
":jxl_lib",
],
visibility = [
"//visibility:public",
],
)

65
third-party/libjxl/build-libjxl-bazel.sh vendored Executable file
View File

@ -0,0 +1,65 @@
#! /bin/sh
set -e
ARCH="$1"
SOURCE_DIR="$2"
BUILD_DIR=$(echo "$(cd "$(dirname "$3")"; pwd -P)/$(basename "$3")")
RSSS="9"
CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DJPEGXL_ENABLE_BENCHMARK=0 -DJPEGXL_ENABLE_FUZZERS=0 -DJPEGXL_ENABLE_TOOLS=0 -DJPEGXL_ENABLE_JPEGLI=0 -DJPEGXL_ENABLE_DOXYGEN=0 -DJPEGXL_ENABLE_MANPAGES=0 -DJPEGXL_ENABLE_BENCHMARK=0 -DJPEGXL_ENABLE_EXAMPLES=0 -DJPEGXL_BUNDLE_LIBPNG=0 -DJPEGXL_ENABLE_JNI=0 -DJPEGXL_ENABLE_SJPEG=0 -DJPEGXL_ENABLE_OPENEXR=0 -DJPEGXL_ENABLE_TRANSCODE_JPEG=0 -DJPEGXL_STATIC=1 -DJPEGXL_ENABLE_BOXES=0"
if [ "$ARCH" = "arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneOS.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneOS*.sdk)
export CFLAGS="-Wall -arch arm64 -miphoneos-version-min=11.0 -funwind-tables"
cd "$BUILD_DIR"
mkdir build
cd build
touch toolchain.cmake
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $CMAKE_OPTIONS ../libjxl
make
elif [ "$ARCH" = "sim_arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
export CFLAGS="-Wall -arch arm64 --target=arm64-apple-ios11.0-simulator -miphonesimulator-version-min=11.0 -funwind-tables"
cd "$BUILD_DIR"
mkdir build
cd build
touch toolchain.cmake
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $CMAKE_OPTIONS ../libjxl
make
elif [ "$ARCH" = "x86_64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
export CFLAGS="-Wall -arch x86_64 -miphoneos-version-min=11.0 -funwind-tables"
cd "$BUILD_DIR"
mkdir build
cd build
touch toolchain.cmake
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
echo "set(CMAKE_SYSTEM_PROCESSOR AMD64)" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $CMAKE_OPTIONS ../libjxl
make
else
echo "Unsupported architecture $ARCH"
exit 1
fi

73
third-party/libjxl/libjxl/AUTHORS vendored Normal file
View File

@ -0,0 +1,73 @@
# List of the project authors for copyright purposes. When contributing to the
# project add your name or your organization's name to this list. See
# CONTRIBUTING.md for details.
#
# For organizations:
# Organization <email pattern: *@domain>
#
# For individuals:
# Name <email address>
#
# Please keep each list sorted. If you wish to change your email address please
# send a pull request.
# Organizations:
Cloudinary Ltd. <*@cloudinary.com>
Google LLC <*@google.com>
# Individuals:
a-shvedov
Alex Xu (Hello71) <alex_y_xu@yahoo.ca>
Alexander Sago <cagelight@gmail.com>
Alistair Barrow
Andrius Lukas Narbutas <andrius4669@gmail.com>
Aous Naman <aous@unsw.edu.au>
Artem Selishchev
Biswapriyo Nath <nathbappai@gmail.com>
CanadianBaconBoi <beamconnor@gmail.com>
Damiano Albani <damiano.albani@gmail.com>
Daniel Novomeský <dnovomesky@gmail.com>
David Burnett <vargolsoft@gmail.com>
dependabot[bot]
Diego Pino <dpino@igalia.com>
Dirk Lemstra <dirk@lemstra.org>
Don Olmstead <don.j.olmstead@gmail.com>
Dong Xu <xdong181@gmail.com>
Even Rouault <even.rouault@spatialys.com>
Fred Brennan <copypaste@kittens.ph>
gi-man
Gilles Devillers (GilDev) <gildev@gmail.com>
Heiko Becker <heirecka@exherbo.org>
Jim Robinson <jimbo2150@gmail.com>
Jon Sneyers <jon@cloudinary.com>
Jonathan Brown (Jonnyawsom3) <jonathanbr30@gmail.com>
Joshua Root <jmr@macports.org>
Kai Hollberg <Schweinepriester@users.noreply.github.com>
Kleis Auke Wolthuizen <github@kleisauke.nl>
L. E. Segovia
Leo Izen <leo.izen@gmail.com>
Lovell Fuller
Maarten DB <anonymous.maarten@gmail.com>
Marcin Konicki <ahwayakchih@gmail.com>
Martin Strunz
Mathieu Malaterre <mathieu.malaterre@gmail.com>
Mikk Leini <mikk.leini@krakul.eu>
Misaki Kasumi <misakikasumi@outlook.com>
Moonchild Straver <moonchild@palemoon.org>
Nicholas Hayes <0xC0000054@users.noreply.github.com>
Nigel Tao <nigeltao@golang.org>
Petr Diblík
Pieter Wuille
roland-rollo
Samuel Leong <wvvwvvvvwvvw@gmail.com>
Sandro <sandro.jaeckel@gmail.com>
Sergey Fedorov <vital.had@gmail.com>
Stephan T. Lavavej <stl@nuwen.net>
Sylvestre Ledru <sylvestre@debian.org>
Thomas Bonfort <thomas.bonfort@airbus.com>
tmkk <tmkkmac@gmail.com>
Vincent Torri <vincent.torri@gmail.com>
xiota
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>
Ziemowit Zabawa <ziemek.zabawa@outlook.com>
源文雨 <41315874+fumiama@users.noreply.github.com>

22
third-party/libjxl/libjxl/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,22 @@
package(default_visibility = ["//:__subpackages__"])
filegroup(
name = "testdata",
srcs = glob([
"testdata/**/*.icc",
"testdata/**/*.pam",
"testdata/**/*.pfm",
"testdata/**/*.pgm",
"testdata/**/*.pnm",
"testdata/**/*.ppm",
"testdata/**/*.png",
"testdata/**/*.jpg",
"testdata/**/*.jxl",
"testdata/**/*.gif",
"testdata/**/*.y4m",
"testdata/**/*.jxl",
"testdata/**/*.png",
"testdata/**/*.jpg",
"testdata/position_encoding/*.txt",
]),
)

85
third-party/libjxl/libjxl/BUILDING.md vendored Normal file
View File

@ -0,0 +1,85 @@
# Compilation
For more details and other workflows see the "Advanced guide" below.
## Checking out the code
```bash
git clone https://github.com/libjxl/libjxl.git --recursive --shallow-submodules
```
This repository uses git submodules to handle some third party dependencies
under `third_party`, that's why it is important to pass `--recursive`. If you
didn't check out with `--recursive`, or any submodule has changed, run:
```bash
git submodule update --init --recursive --depth 1 --recommend-shallow
```
The `--shallow-submodules` and `--depth 1 --recommend-shallow` options create
shallow clones which only downloads the commits requested, and is all that is
needed to build `libjxl`. Should full clones be necessary, you could always run:
```bash
git submodule foreach git fetch --unshallow
git submodule update --init --recursive
```
which pulls the rest of the commits in the submodules.
Important: If you downloaded a zip file or tarball from the web interface you
won't get the needed submodules and the code will not compile. You can download
these external dependencies from source running `./deps.sh`. The git workflow
described above is recommended instead.
## Installing dependencies
Required dependencies for compiling the code, in a Debian/Ubuntu based
distribution run:
```bash
sudo apt install cmake pkg-config libbrotli-dev
```
Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools,
in a Debian/Ubuntu based distribution run:
```bash
sudo apt install libgif-dev libjpeg-dev libopenexr-dev libpng-dev libwebp-dev
```
We recommend using a recent Clang compiler (version 7 or newer), for that
install clang and set `CC` and `CXX` variables.
```bash
sudo apt install clang
export CC=clang CXX=clang++
```
## Building
```bash
cd libjxl
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF ..
cmake --build . -- -j$(nproc)
```
The encoder/decoder tools will be available in the `build/tools` directory.
## <a name="installing"></a> Installing
```bash
sudo cmake --install .
```
## Building JPEG XL for developers
For experienced developers, we provide build instructions for several other environments:
* [Building on Debian](doc/developing_in_debian.md)
* Building on Windows with [vcpkg](doc/developing_in_windows_vcpkg.md) (Visual Studio 2019)
* Building on Windows with [MSYS2](doc/developing_in_windows_msys.md)
* [Cross Compiling for Windows with Crossroad](doc/developing_with_crossroad.md)

View File

@ -0,0 +1,20 @@
## Disclaimer
Haiku builds are not officially supported, i.e. the build might not work at all,
some tests may fail and some sub-projects are excluded from build.
This manual outlines Haiku-specific setup. For general building and testing
instructions see "[BUILDING](BUILDING.md)" and
"[Building and Testing changes](doc/building_and_testing.md)".
## Dependencies
```shell
pkgman install llvm9_clang ninja cmake doxygen libjpeg_turbo_devel giflib_devel
```
## Building
```shell
TEST_STACK_LIMIT=none CMAKE_FLAGS="-I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++ -I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++/x86_64-unknown-haiku" CMAKE_SHARED_LINKER_FLAGS="-shared -Xlinker -soname=libjpegxl.so -lpthread" ./ci.sh opt
```

View File

@ -0,0 +1,41 @@
## Disclaimer
OSX builds have "best effort" support, i.e. build might not work at all, some
tests may fail and some sub-projects are excluded from build.
This manual outlines OSX specific setup. For general building and testing
instructions see "[BUILDING](BUILDING.md)" and
"[Building and Testing changes](doc/building_and_testing.md)".
[Homebrew](https://brew.sh/) is a popular package manager. JPEG XL library and
binaries could be installed using it:
```bash
brew install jpeg-xl
```
## Dependencies
Make sure that `brew doctor` does not report serious problems and up-to-date
version of XCode is installed.
Installing (actually, building) `clang` might take a couple hours.
```bash
brew install llvm
```
```bash
brew install coreutils cmake giflib jpeg-turbo libpng ninja zlib
```
Before building the project check that `which clang` is
`/usr/local/opt/llvm/bin/clang`, not the one provided by XCode. If not, update
`PATH` environment variable.
Also, setting `CMAKE_PREFIX_PATH` might be necessary for correct include paths
resolving, e.g.:
```bash
export CMAKE_PREFIX_PATH=`brew --prefix giflib`:`brew --prefix jpeg-turbo`:`brew --prefix libpng`:`brew --prefix zlib`
```

320
third-party/libjxl/libjxl/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,320 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- encoder API: add `JxlEncoderSetExtraChannelDistance` to adjust the quality
of extra channels (like alpha) separately.
- encoder API: new api functions for streaming encoding:
- `JxlEncoderSetOutputCallback`,
- `JxlEncoderChunkedImageFrameStart`,
- `JxlEncoderChunkedImageFrameAddPart` and new
- `JXL_ENC_FRAME_SETTING_BUFFERING` enum value.
- encoder API: new options for more fine-grained control over metadata
preservation when using `JxlEncoderAddJPEGFrame`:
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF`
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP`
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF`
- encoder API: new function `JxlEncoderSetUpsamplingMode` to change the upsampling
method, e.g. to use nearest-neighbor upsampling for pixel art
- cjxl can now be used to explicitly add/update/strip Exif/XMP/JUMBF metadata using
the decoder-hints syntax, e.g. `cjxl input.ppm -x exif=input.exif output.jxl`
- djxl can now be used to extract Exif/XMP/JUMBF metadata
### Removed
- API: the Butteraugli API (`jxl/butteraugli.h`) was removed.
- encoder and decoder API: all deprecated functions were removed:
`JxlDecoderDefaultPixelFormat`, `JxlEncoderOptionsSetLossless`,
`JxlEncoderOptionsSetEffort`, `JxlEncoderOptionsSetDecodingSpeed`,
`JxlEncoderOptionsSetDistance`, `JxlEncoderOptionsCreate`, as well as
the deprecated enumerator values `JXL_DEC_EXTENSIONS`, `JXL_ENC_NOT_SUPPORTED`,
`JXL_TYPE_BOOLEAN`, `JXL_TYPE_UINT32`, and deprecated type `JxlEncoderOptions`.
- decoder API: the signature of `JxlDecoderGetColorAsEncodedProfile`,
`JxlDecoderGetICCProfileSize`, and `JxlDecoderGetColorAsICCProfile`
changed: a deprecated unused argument was removed.
### Changed
- changed the name of the cjxl flag `photon_noise` to `photon_noise_iso`
## [0.8.0] - 2023-01-18
### Added
- decoder API: new function `JxlDecoderSetImageBitDepth` to set the bit depth
of the output buffer.
- decoder API proposal: add `JxlDecoderSetOutputColorProfile` and
`JxlDecoderSetCms` to enable decoding to desired colorspace; NB: not
implemented yet.
- encoder API: new function `JxlEncoderSetFrameBitDepth` to set the bit depth
of the input buffer.
- encoder API: add an effort 10 option for lossless compression; using this
setting requires calling `JxlEncoderAllowExpertOptions`.
- encoder API: new `JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES` enum value to
allow explicit control of metadata compression
### Removed
- common API: removed `JxlIntrinsicSizeHeader`
- decoder API: removed deprecated `JXL_DEC_NEED_DC_OUT_BUFFER` and
`JXL_DEC_DC_IMAGE` events, `JxlDecoderDCOutBufferSize` and
`JxlDecoderSetDCOutBuffer` functions
### Changed / clarified
- encoder API: `JxlEncoderProcessOutput` requires at least 32 bytes of output
space to proceed and guarantees that at least one byte will be written
## [0.7] - 2022-07-21
### Added
- Export version information in headers.
- decoder API: Ability to decode the content of metadata boxes:
`JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
`JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and
`JxlDecoderSetDecompressBoxes`.
- decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`.
- decoder API: ability to request updates on different progressive events using
`JxlDecoderSetProgressiveDetail`; currently supported events are
`kDC`, `kLastPasses` and `kPasses`.
- decoder API: ability to specify desired intensity target using
`JxlDecoderSetDesiredIntensityTarget`
- decoder API: new function `JxlDecoderSetCoalesced` to allow decoding
non-coalesced (unblended) frames, e.g. layers of a composite still image
or the cropped frames of a recompressed GIF/APNG.
- decoder API: new function `JxlDecoderSetUnpremultiplyAlpha` to set
preference for getting an associated alpha channel with premultiplied or
unpremultiplied colors.
- decoder API: field added to `JxlFrameHeader`: a `JxlLayerInfo` struct
that contains crop dimensions and offsets and blending information for
the non-coalesced case.
- decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get
the blending information for extra channels in the non-coalesced case.
- decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`,
allowing output callbacks to receive more information about the number of
threads on which they are running.
- decoder API: new function `JxlDecoderSkipCurrentFrame` to skip processing
the current frame after a progressive detail is reached.
- decoder API: new function `JxlDecoderGetIntendedDownsamplingRatio` to get
the intended downsampling ratio of progressive steps, based on the
information in the frame header.
- decoder API: new function `JxlDecoderSetRenderSpotcolors` to allow disabling
rendering of spot colors.
- decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
and `intrinsic_ysize` to signal the intrinsic size.
- encoder API: ability to add metadata boxes, added new functions
`JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and
`JxlEncoderCloseFrames`.
- encoder API: added ability to set several encoder options / extra fields to
frames using `JxlEncoderSetFrameName`, `JxlEncoderFrameSettingsSetOption`,
`JxlEncoderFrameSettingsSetFloatOption`.
- encoder API: added ability to check required codestream compatibility level
and force specified using `JxlEncoderGetRequiredCodestreamLevel` and
`JxlEncoderSetCodestreamLevel`.
- encoder API: added ability to force emitting box-based container format
using `JxlEncoderUseContainer`.
- encoder API: added ability to store JPEG metadata for lossless reconstruction
using `JxlEncoderStoreJPEGMetadata`
- encoder API: new functions `JxlEncoderSetFrameHeader` and
`JxlEncoderSetExtraChannelBlendInfo` to set animation
and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and
`JxlEncoderInitBlendInfo` to initialize the structs to set.
- encoder API: ability to encode arbitrary extra channels:
`JxlEncoderInitExtraChannelInfo`, `JxlEncoderSetExtraChannelInfo`,
`JxlEncoderSetExtraChannelName` and `JxlEncoderSetExtraChannelBuffer`.
- encoder API: ability to plug custom CMS implementation using
`JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms)`
- encoder API: added `JxlEncoderGetError` to retrieve last encoder error.
### Changed
- decoder API: using `JxlDecoderCloseInput` at the end of all input is required
when using JXL_DEC_BOX, and is now also encouraged in other cases, but not
required in those other cases for backwards compatibility.
- encoder API: `JxlEncoderCloseInput` now closes both frames and boxes input.
- CLI: `cjxl` and `djxl` have been reimplemented on the base of public decoder
and encoder API; dropped dependency on `gflags` for argument parsing.
### Deprecated
- decoder API: `JXL_DEC_EXTENSIONS` event: use `JXL_DEC_BASIC_INFO`
- decoder / encoder API: pixel types `JXL_TYPE_BOOLEAN` and `JXL_TYPE_UINT32`:
consider using `JXL_TYPE_UINT8` and `JXL_TYPE_FLOAT` correspondingly.
- decoder API: pixel format parameter for `JxlDecoderGetColorAsEncodedProfile`
and `JxlDecoderGetICCProfileSize`: pass `NULL`.
- decoder API: `JxlDecoderDefaultPixelFormat`
- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead.
- encoder API: `JxlEncoderOptionsCreate`: use `JxlEncoderFrameSettingsCreate`
instead.
- encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance`
instead.
- encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless`
instead.
- encoder API: `JxlEncoderOptionsSetEffort`: use
`JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)`
instead.
- encoder API: `JxlEncoderOptionsSetDecodingSpeed`: use
`JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)`
instead.
- encoder API: deprecated `JXL_ENC_NOT_SUPPORTED`, the encoder returns
`JXL_ENC_ERROR` instead and there is no need to handle
`JXL_ENC_NOT_SUPPORTED`.
## [0.6.1] - 2021-10-29
### Changed
- Security: Fix OOB read in splines rendering (#735 -
[CVE-2021-22563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22563))
- Security: Fix OOB copy (read/write) in out-of-order/multi-threaded decoding
(#708 - [CVE-2021-22564](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22564))
- Fix segfault in `djxl` tool with `--allow_partial_files` flag (#781).
- Fix border in extra channels when using upsampling (#796)
## [0.6] - 2021-10-04
### Added
- API: New functions to decode extra channels:
`JxlDecoderExtraChannelBufferSize` and `JxlDecoderSetExtraChannelBuffer`.
- API: New function `JxlEncoderInitBasicInfo` to initialize `JxlBasicInfo`
(only needed when encoding). NOTE: it is now required to call this function
when using the encoder. Padding was added to the struct for forward
compatibility.
- API: Support for encoding oriented images.
- API: FLOAT16 support in the encoder API.
- Rewrite of the GDK pixbuf loader plugin. Added proper color management and
animation support.
- Rewrite of GIMP plugin. Added compression parameters dialog and switched to
using the public C API.
- Debian packages for GDK pixbuf loader (`libjxl-gdk-pixbuf`) and GIMP
(`libjxl-gimp-plugin`) plugins.
- `cjxl`/`djxl` support for `stdin` and `stdout`.
### Changed
- API: Renamed the field `alpha_associated` in `JxlExtraChannelInfo` to
`alpha_premultiplied`, to match the corresponding name in `JxlBasicInfo`.
- Improved the 2x2 downscaling method in the encoder for the optional color
channel resampling for low bit rates.
- Fixed: the combination of floating point original data, XYB color encoding,
and Modular mode was broken (in both encoder and decoder). It now works.
NOTE: this can cause the current encoder to write jxl bitstreams that do
not decode with the old decoder. In particular this will happen when using
cjxl with PFM, EXR, or floating point PSD input, and a combination of XYB
and modular mode is used (which caused an encoder error before), e.g.
using options like `-m -q 80` (lossy modular), `-d 4.5` or `--progressive_dc=1`
(modular DC frame), or default lossy encoding on an image where patches
end up being used. There is no problem when using cjxl with PNG, JPEG, GIF,
APNG, PPM, PGM, PGX, or integer (8-bit or 16-bit) PSD input.
- `libjxl` static library now bundles skcms, fixing static linking in
downstream projects when skcms is used.
- Spline rendering performance improvements.
- Butteraugli changes for less visual masking.
## [0.5] - 2021-08-02
### Added
- API: New function to decode the image using a callback outputting a part of a
row per call.
- API: 16-bit float output support.
- API: `JxlDecoderRewind` and `JxlDecoderSkipFrames` functions to skip more
efficiently to earlier animation frames.
- API: `JxlDecoderSetPreferredColorProfile` function to choose color profile in
certain circumstances.
- encoder: Adding `center_x` and `center_y` flags for more control of the tile
order.
- New encoder speeds `lightning` (1) and `thunder` (2).
### Changed
- Re-licensed the project under a BSD 3-Clause license. See the
[LICENSE](LICENSE) and [PATENTS](PATENTS) files for details.
- Full JPEG XL part 1 specification support: Implemented all the spec required
to decode files to pixels, including cases that are not used by the encoder
yet. Part 2 of the spec (container format) is final but not fully implemented
here.
- Butteraugli metric improvements. Exact numbers are different from previous
versions.
- Memory reductions during decoding.
- Reduce the size of the jxl_dec library by removing dependencies.
- A few encoding speedups.
- Clarify the security policy.
- Significant encoding improvements (~5 %) and less ringing.
- Butteraugli metric to have some less masking.
- `cjxl` flag `--speed` is deprecated and replaced by the `--effort` synonym.
### Removed
- API for returning a downsampled DC was deprecated
(`JxlDecoderDCOutBufferSize` and `JxlDecoderSetDCOutBuffer`) and will be
removed in the next release.
## [0.3.7] - 2021-03-29
### Changed
- Fix a rounding issue in 8-bit decoding.
## [0.3.6] - 2021-03-25
### Changed
- Fix a bug that could result in the generation of invalid codestreams as
well as failure to decode valid streams.
## [0.3.5] - 2021-03-23
### Added
- New encode-time options for faster decoding at the cost of quality.
- Man pages for cjxl and djxl.
### Changed
- Memory usage improvements.
- Faster decoding to 8-bit output with the C API.
- GIMP plugin: avoid the sRGB conversion dialog for sRGB images, do not show
a console window on Windows.
- Various bug fixes.
## [0.3.4] - 2021-03-16
### Changed
- Improved box parsing.
- Improved metadata handling.
- Performance and memory usage improvements.
## [0.3.3] - 2021-03-05
### Changed
- Performance improvements for small images.
- Add a (flag-protected) non-high-precision mode with better speed.
- Significantly speed up the PQ EOTF.
- Allow optional HDR tone mapping in djxl (--tone_map, --display_nits).
- Change the behavior of djxl -j to make it consistent with cjxl (#153).
- Improve image quality.
- Improve EXIF handling.
## [0.3.2] - 2021-02-12
### Changed
- Fix embedded ICC encoding regression
[#149](https://gitlab.com/wg1/jpeg-xl/-/issues/149).
## [0.3.1] - 2021-02-10
### Changed
- New experimental Butteraugli API (`jxl/butteraugli.h`).
- Encoder improvements to low quality settings.
- Bug fixes, including fuzzer-found potential security bug fixes.
- Fixed `-q 100` and `-d 0` not triggering lossless modes.
## [0.3] - 2021-01-29
### Changed
- Minor change to the Decoder C API to accommodate future work for other ways
to provide input.
- Future decoder C API changes will be backwards compatible.
- Lots of bug fixes since the previous version.
## [0.2] - 2020-12-24
### Added
- JPEG XL bitstream format is frozen. Files encoded with 0.2 will be supported
by future versions.
### Changed
- Files encoded with previous versions are not supported.
## [0.1.1] - 2020-12-01
## [0.1] - 2020-11-14
### Added
- Initial release of an encoder (`cjxl`) and decoder (`djxl`) that work
together as well as a benchmark tool for comparison with other codecs
(`benchmark_xl`).
- Note: JPEG XL format is in the final stages of standardization, minor changes
to the codestream format are still possible but we are not expecting any
changes beyond what is required by bug fixing.
- API: new decoder API in C, check the `examples/` directory for its example
usage. The C API is a work in progress and likely to change both in API and
ABI in future releases.

527
third-party/libjxl/libjxl/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,527 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Ubuntu bionic ships with cmake 3.10.
cmake_minimum_required(VERSION 3.10)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Honor VISIBILITY_INLINES_HIDDEN on all types of targets.
if(POLICY CMP0063)
cmake_policy(SET CMP0063 NEW)
endif()
# Pass CMAKE_EXE_LINKER_FLAGS to CC and CXX compilers when testing if they work.
if(POLICY CMP0065)
cmake_policy(SET CMP0065 NEW)
endif()
# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in 3.14.
if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW)
endif()
project(LIBJXL LANGUAGES C CXX)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(
"int main() {
#if !defined(__EMSCRIPTEN__)
static_assert(false, \"__EMSCRIPTEN__ is not defined\");
#endif
return 0;
}"
JPEGXL_EMSCRIPTEN
)
message(STATUS "CMAKE_SYSTEM_PROCESSOR is ${CMAKE_SYSTEM_PROCESSOR}")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fsanitize=fuzzer-no-link" CXX_FUZZERS_SUPPORTED)
check_cxx_compiler_flag("-Xclang -mconstructor-aliases" CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
check_cxx_compiler_flag("-fmacro-prefix-map=OLD=NEW" CXX_MACRO_PREFIX_MAP)
check_cxx_compiler_flag("-fno-rtti" CXX_NO_RTTI_SUPPORTED)
# Enabled PIE binaries by default if supported.
include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED)
if(CHECK_PIE_SUPPORTED)
check_pie_supported(LANGUAGES CXX)
if(CMAKE_CXX_LINK_PIE_SUPPORTED)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
endif()
endif()
if(PROVISION_DEPENDENCIES)
# Run script to provision dependencies.
find_program (BASH_PROGRAM bash)
if(BASH_PROGRAM)
execute_process(
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/deps.sh
RESULT_VARIABLE PROVISION_DEPENDENCIES_RESULT)
endif()
if(NOT PROVISION_DEPENDENCIES_RESULT EQUAL "0")
message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR}/deps.sh failed with ${PROVISION_DEPENDENCIES_RESULT}")
endif()
endif()
### Project build options:
if(CXX_FUZZERS_SUPPORTED)
# Enabled by default except on arm64, Windows and Apple builds.
set(ENABLE_FUZZERS_DEFAULT true)
endif()
find_package(PkgConfig)
if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
pkg_check_modules(TCMallocMinimalVersionCheck QUIET IMPORTED_TARGET
libtcmalloc_minimal)
if(TCMallocMinimalVersionCheck_FOUND AND
NOT TCMallocMinimalVersionCheck_VERSION VERSION_EQUAL 2.8.0)
# Enabled by default except on Windows and Apple builds for
# tcmalloc != 2.8.0. tcmalloc 2.8.1 already has a fix for this issue.
set(ENABLE_TCMALLOC_DEFAULT true)
else()
message(STATUS
"tcmalloc version ${TCMallocMinimalVersionCheck_VERSION} -- "
"tcmalloc 2.8.0 disabled due to "
"https://github.com/gperftools/gperftools/issues/1204")
endif()
endif()
check_cxx_source_compiles(
"int main() {
#if !defined(HWY_DISABLED_TARGETS)
static_assert(false, \"HWY_DISABLED_TARGETS is not defined\");
#endif
return 0;
}"
JXL_HWY_DISABLED_TARGETS_FORCED
)
set(WARNINGS_AS_ERRORS_DEFAULT false)
if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN)
set(BUNDLE_LIBPNG_DEFAULT YES)
else()
set(BUNDLE_LIBPNG_DEFAULT NO)
endif()
# Standard cmake naming for building shared libraries.
get_property(SHARED_LIBS_SUPPORTED GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS)
option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ${SHARED_LIBS_SUPPORTED})
set(JPEGXL_ENABLE_FUZZERS ${ENABLE_FUZZERS_DEFAULT} CACHE BOOL
"Build JPEGXL fuzzer targets.")
set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL
"Build JPEGXL developer tools.")
set(JPEGXL_ENABLE_TOOLS true CACHE BOOL
"Build JPEGXL user tools: cjxl and djxl.")
set(JPEGXL_ENABLE_JPEGLI true CACHE BOOL
"Build jpegli library.")
set(JPEGXL_ENABLE_JPEGLI_LIBJPEG true CACHE BOOL
"Build libjpeg.so shared library based on jpegli.")
set(JPEGXL_INSTALL_JPEGLI_LIBJPEG false CACHE BOOL
"Install jpegli version of libjpeg.so system-wide.")
set(JPEGLI_LIBJPEG_LIBRARY_VERSION "62.3.0" CACHE STRING
"Library version of the libjpeg.so shared library that we build.")
set(JPEGLI_LIBJPEG_LIBRARY_SOVERSION "62" CACHE STRING
"Library so-version of the libjpeg.so shared library that we build.")
set(JPEGXL_ENABLE_DOXYGEN true CACHE BOOL
"Generate C API documentation using Doxygen.")
set(JPEGXL_ENABLE_MANPAGES true CACHE BOOL
"Build and install man pages for the command-line tools.")
set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL
"Build JPEGXL benchmark tools.")
set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL
"Build JPEGXL library usage examples.")
set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL
"Build libpng from source and link it statically.")
set(JPEGXL_ENABLE_JNI true CACHE BOOL
"Build JPEGXL JNI Java wrapper, if Java dependencies are installed.")
set(JPEGXL_ENABLE_SJPEG true CACHE BOOL
"Build JPEGXL with support for encoding with sjpeg.")
set(JPEGXL_ENABLE_OPENEXR true CACHE BOOL
"Build JPEGXL with support for OpenEXR if available.")
set(JPEGXL_ENABLE_SKCMS true CACHE BOOL
"Build with skcms instead of lcms2.")
set(JPEGXL_BUNDLE_SKCMS true CACHE BOOL
"When building with skcms, bundle it into libjxl.a.")
set(JPEGXL_ENABLE_VIEWERS false CACHE BOOL
"Build JPEGXL viewer tools for evaluation.")
set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL
"Build JPEGXL using gperftools (tcmalloc) allocator.")
set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL
"Build third-party plugins to support JPEG XL in other applications.")
set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL
"Enable code coverage tracking for libjxl. This also enables debug and disables optimizations.")
set(JPEGXL_ENABLE_SIZELESS_VECTORS false CACHE BOOL
"Builds in support for SVE/RVV vectorization")
set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL
"Builds in support for decoding transcoded JXL files back to JPEG,\
disabling it makes the decoder reject JXL_DEC_JPEG_RECONSTRUCTION events,\
(default enabled)")
set(JPEGXL_ENABLE_BOXES true CACHE BOOL
"Builds in support for decoding boxes in JXL files,\
disabling it makes the decoder reject JXL_DEC_BOX events,\
(default enabled)")
set(JPEGXL_STATIC false CACHE BOOL
"Build tools as static binaries.")
set(JPEGXL_WARNINGS_AS_ERRORS ${WARNINGS_AS_ERRORS_DEFAULT} CACHE BOOL
"Treat warnings as errors during compilation.")
set(JPEGXL_DEP_LICENSE_DIR "" CACHE STRING
"Directory where to search for system dependencies \"copyright\" files.")
set(JPEGXL_FORCE_NEON false CACHE BOOL
"Set flags to enable NEON in arm if not enabled by your toolchain.")
set(JPEGXL_TEST_TOOLS false CACHE BOOL
"Run scripts that test the encoding / decoding tools.")
set(JPEGXL_ENABLE_AVX512 false CACHE BOOL
"Build with AVX512 support (faster on CPUs that support it, but larger binary size).")
set(JPEGXL_ENABLE_AVX512_ZEN4 false CACHE BOOL
"Build with Zen4-optimized AVX512 support (faster on CPUs that support it, but larger binary size).")
# Force system dependencies.
set(JPEGXL_FORCE_SYSTEM_BROTLI false CACHE BOOL
"Force using system installed brotli instead of third_party/brotli source.")
set(JPEGXL_FORCE_SYSTEM_GTEST false CACHE BOOL
"Force using system installed googletest (gtest/gmock) instead of third_party/googletest source.")
set(JPEGXL_FORCE_SYSTEM_LCMS2 false CACHE BOOL
"Force using system installed lcms2 instead of third_party/lcms source.")
set(JPEGXL_FORCE_SYSTEM_HWY false CACHE BOOL
"Force using system installed highway (libhwy-dev) instead of third_party/highway source.")
# Check minimum compiler versions. Older compilers are not supported and fail
# with hard to understand errors.
if (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)
message(FATAL_ERROR "Different C/C++ compilers set: "
"${CMAKE_C_COMPILER_ID} vs ${CMAKE_CXX_COMPILER_ID}")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Android NDK's toolchain.cmake fakes the clang version in
# CMAKE_CXX_COMPILER_VERSION with an incorrect number, so ignore this.
if (NOT CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION MATCHES "clang"
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
message(FATAL_ERROR
"Minimum Clang version required is Clang 5, please update.")
endif()
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7)
message(FATAL_ERROR
"Minimum GCC version required is 7, please update.")
endif()
endif()
message(STATUS
"Compiled IDs C:${CMAKE_C_COMPILER_ID}, C++:${CMAKE_CXX_COMPILER_ID}")
# Always disable SSSE3 since it is rare to have SSSE3 but not SSE4
set(HWY_DISABLED_TARGETS "HWY_SSSE3")
if (NOT JPEGXL_ENABLE_AVX512)
message(STATUS "Disabled AVX512 (set JPEGXL_ENABLE_AVX512 to enable it)")
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3")
add_definitions(-DFJXL_ENABLE_AVX512=0)
endif()
if (NOT JPEGXL_ENABLE_AVX512_ZEN4)
message(STATUS "Disabled AVX512_ZEN4 (set JPEGXL_ENABLE_AVX512_ZEN4 to enable it)")
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3_ZEN4")
endif()
# CMAKE_EXPORT_COMPILE_COMMANDS is used to generate the compilation database
# used by clang-tidy.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(JPEGXL_STATIC)
set(BUILD_SHARED_LIBS 0)
# Clang developers say that in case to use "static" we have to build stdlib
# ourselves; for real use case we don't care about stdlib, as it is "granted",
# so just linking all other libraries is fine.
if (NOT MSVC AND NOT APPLE)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
endif()
endif() # JPEGXL_STATIC
# Threads
set(THREADS_PREFER_PTHREAD_FLAG YES)
find_package(Threads REQUIRED)
# These settings are important to drive check_cxx_source_compiles
# See CMP0067 (min cmake version is 3.10 anyway)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
# Atomics
find_package(Atomics REQUIRED)
if(JPEGXL_STATIC)
if (MINGW)
# In MINGW libstdc++ uses pthreads directly. When building statically a
# program (regardless of whether the source code uses pthread or not) the
# toolchain will add stdc++ and pthread to the linking step but stdc++ will
# be linked statically while pthread will be linked dynamically.
# To avoid this and have pthread statically linked with need to pass it in
# the command line with "-Wl,-Bstatic -lpthread -Wl,-Bdynamic" but the
# linker will discard it if not used by anything else up to that point in
# the linker command line. If the program or any dependency don't use
# pthread directly -lpthread is discarded and libstdc++ (added by the
# toolchain later) will then use the dynamic version. For this we also need
# to pass -lstdc++ explicitly before -lpthread. For pure C programs -lstdc++
# will be discarded anyway.
# This adds these flags as dependencies for *all* targets. Adding this to
# CMAKE_EXE_LINKER_FLAGS instead would cause them to be included before any
# object files and therefore discarded. This should be set in the
# INTERFACE_LINK_LIBRARIES of Threads::Threads but some third_part targets
# don't depend on it.
link_libraries(-Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic)
elseif(CMAKE_USE_PTHREADS_INIT)
# "whole-archive" is not supported on OSX.
if (NOT APPLE)
# Set pthreads as a whole-archive, otherwise weak symbols in the static
# libraries will discard pthreads symbols leading to segmentation fault at
# runtime.
message(STATUS "Using -lpthread as --whole-archive")
set_target_properties(Threads::Threads PROPERTIES
INTERFACE_LINK_LIBRARIES
"-Wl,--whole-archive;-lpthread;-Wl,--no-whole-archive")
endif()
endif()
endif() # JPEGXL_STATIC
if (JPEGXL_EMSCRIPTEN)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
endif()
if (CXX_MACRO_PREFIX_MAP)
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
endif()
if (CXX_NO_RTTI_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif()
# Internal flags for coverage builds:
set(JPEGXL_COVERAGE_FLAGS)
set(JPEGXL_COVERAGE_LINK_FLAGS)
if (MSVC)
# TODO(janwas): add flags
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
else ()
# Global compiler flags for all targets here and in subdirectories.
add_definitions(
# Avoid changing the binary based on the current time and date.
-D__DATE__="redacted"
-D__TIMESTAMP__="redacted"
-D__TIME__="redacted"
)
# TODO(eustas): JXL currently compiles, but does not pass tests...
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED)
if (NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV")
endif()
add_definitions(-DHWY_DISABLED_TARGETS=\(${HWY_DISABLED_TARGETS}\))
endif()
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.
# For this reason we place them in CMAKE_CXX_FLAGS instead.
# See https://gitlab.kitware.com/cmake/cmake/issues/15826
# Machine flags.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
endif()
if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
endif()
if(WIN32)
# Not supported by clang-cl, but frame pointers are default on Windows
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
endif()
# CPU flags - remove once we have NEON dynamic dispatch
# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed.
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
if(JPEGXL_FORCE_NEON)
# GCC requires these flags, otherwise __ARM_NEON is undefined.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
-mfpu=neon-vfpv4 -mfloat-abi=hard")
endif()
endif()
# Force build with optimizations in release mode.
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
add_compile_options(
# Ignore this to allow redefining __DATE__ and others.
-Wno-builtin-macro-redefined
# Global warning settings.
-Wall
)
if (JPEGXL_WARNINGS_AS_ERRORS)
add_compile_options(-Werror)
endif ()
if(JPEGXL_ENABLE_COVERAGE)
set(JPEGXL_COVERAGE_FLAGS
-g -O0 -fprofile-arcs -ftest-coverage
-DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0
)
set(JPEGXL_COVERAGE_LINK_FLAGS
--coverage
)
endif() # JPEGXL_ENABLE_COVERAGE
endif () # !MSVC
include(GNUInstallDirs)
# Separately build/configure testing frameworks and other third_party libraries
# to allow disabling tests in those libraries.
include(third_party/testing.cmake)
add_subdirectory(third_party)
# Copy the JXL license file to the output build directory.
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
${PROJECT_BINARY_DIR}/LICENSE.jpeg-xl COPYONLY)
# Enable tests regardless of where they are defined.
enable_testing()
include(CTest)
# Specify default location of `testdata`:
if(NOT DEFINED JPEGXL_TEST_DATA_PATH)
set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/testdata")
endif()
# Libraries.
add_subdirectory(lib)
if(BUILD_TESTING)
# Script to run tests over the source code in bash.
find_program (BASH_PROGRAM bash)
if(BASH_PROGRAM)
add_test(
NAME bash_test
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh)
endif()
endif() # BUILD_TESTING
# Documentation generated by Doxygen
if(JPEGXL_ENABLE_DOXYGEN)
find_package(Doxygen)
if(DOXYGEN_FOUND)
set(DOXYGEN_GENERATE_HTML "YES")
set(DOXYGEN_GENERATE_XML "YES")
set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include")
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md")
if(JPEGXL_WARNINGS_AS_ERRORS)
set(DOXYGEN_WARN_AS_ERROR "YES")
endif()
set(DOXYGEN_QUIET "YES")
doxygen_add_docs(doc
"${CMAKE_CURRENT_SOURCE_DIR}/lib/include"
"${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Generating C API documentation")
# Add sphinx doc build step for readthedocs.io (requires doxygen too).
find_program(SPHINX_BUILD_PROGRAM sphinx-build)
if(SPHINX_BUILD_PROGRAM)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent"
COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd"
COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto
${CMAKE_SOURCE_DIR}/doc/sphinx
${CMAKE_CURRENT_BINARY_DIR}/rtd
DEPENDS doc
)
# This command runs the documentation generation every time since the output
# target file doesn't exist.
add_custom_target(rtd-html
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent
)
else() # SPHINX_BUILD_PROGRAM\
message(WARNING "sphinx-build not found, skipping rtd documentation")
endif() # SPHINX_BUILD_PROGRAM
else()
# Create a "doc" target for compatibility since "doc" is not otherwise added to
# the build when doxygen is not installed.
add_custom_target(doc false
COMMENT "Error: Can't generate doc since Doxygen not installed.")
endif() # DOXYGEN_FOUND
endif() # JPEGXL_ENABLE_DOXYGEN
if(JPEGXL_ENABLE_MANPAGES)
find_program(ASCIIDOC a2x)
if(ASCIIDOC)
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash" OR MINGW)
set(ASCIIDOC_PY_FOUND ON)
# Run the program directly and set ASCIIDOC as empty.
set(ASCIIDOC_PY "${ASCIIDOC}")
set(ASCIIDOC "")
elseif(ASCIIDOC_SHEBANG MATCHES "python2")
find_package(Python2 COMPONENTS Interpreter)
set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}")
set(ASCIIDOC_PY Python2::Interpreter)
elseif(ASCIIDOC_SHEBANG MATCHES "python3")
find_package(Python3 COMPONENTS Interpreter)
set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}")
set(ASCIIDOC_PY Python3::Interpreter)
else()
find_package(Python COMPONENTS Interpreter QUIET)
if(NOT Python_Interpreter_FOUND)
find_program(ASCIIDOC_PY python)
if(ASCIIDOC_PY)
set(ASCIIDOC_PY_FOUND ON)
endif()
else()
set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}")
set(ASCIIDOC_PY Python::Interpreter)
endif()
endif()
if (ASCIIDOC_PY_FOUND)
set(MANPAGE_FILES "")
set(MANPAGES "")
foreach(PAGE IN ITEMS cjxl djxl)
# Invoking the Python interpreter ourselves instead of running the a2x binary
# directly is necessary on MSYS2, otherwise it is run through cmd.exe which
# does not recognize it.
add_custom_command(
OUTPUT "${PAGE}.1"
COMMAND "${ASCIIDOC_PY}"
ARGS ${ASCIIDOC}
--format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt")
list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1")
list(APPEND MANPAGES "${PAGE}.1")
endforeach()
add_custom_target(manpages ALL DEPENDS ${MANPAGES})
install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif() # ASCIIDOC_PY_FOUND
else()
message(WARNING "asciidoc was not found, the man pages will not be installed.")
endif() # ASCIIDOC
endif() # JPEGXL_ENABLE_MANPAGES
# Example usage code.
if (JPEGXL_ENABLE_EXAMPLES)
include(examples/examples.cmake)
endif ()
# Plugins for third-party software
if (JPEGXL_ENABLE_PLUGINS)
add_subdirectory(plugins)
endif ()
# Binary tools
add_subdirectory(tools)

View File

@ -0,0 +1,93 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of
experience, education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening,
offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct also applies outside the project spaces when the Project
Steward has a reasonable belief that an individual's behavior may have a
negative impact on the project or its community.
## Conflict Resolution
We do not believe that all conflict is bad; healthy debate and disagreement
often yield positive results. However, it is never okay to be disrespectful or
to engage in behavior that violates the projects code of conduct.
If you see someone violating the code of conduct, you are encouraged to address
the behavior directly with those involved. Many issues can be resolved quickly
and easily, and this gives people more control over the outcome of their
dispute. If you are unable to resolve the matter for any reason, or if the
behavior is threatening or harassing, report it. We are dedicated to providing
an environment where participants feel welcome and safe.
Reports should be directed to Jyrki Alakuijala <jyrki@google.com>, the
Project Steward(s) for JPEG XL. It is the Project Stewards duty to
receive and address reported violations of the code of conduct. They will then
work with a committee consisting of representatives from the Open Source
Programs Office and the Google Open Source Strategy team. If for any reason you
are uncomfortable reaching out to the Project Steward, please email
opensource@google.com.
We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
incidents, which may range from not taking action to permanent expulsion from
the project and project-sponsored spaces. We will notify the accused of the
report and provide them an opportunity to discuss it before any action is taken.
The identity of the reporter will be omitted from the details of the report
supplied to the accused. In potentially harmful situations, such as ongoing
harassment or threats to anyone's safety, we may take action without notice.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

View File

@ -0,0 +1,132 @@
# Contributing to libjxl
## Contributing with bug reports
For security-related issues please see [SECURITY.md](SECURITY.md).
We welcome suggestions, feature requests and bug reports. Before opening a new
issue please take a look if there is already an existing one in the following
link:
* https://github.com/libjxl/libjxl/issues
## Contributing with patches and Pull Requests
We'd love to accept your contributions to the JPEG XL Project. Please read
through this section before sending a Pull Request.
### Contributor License Agreements
Our project is open source under the terms outlined in the [LICENSE](LICENSE)
and [PATENTS](PATENTS) files. Before we can accept your contributions, even for
small changes, there are just a few small guidelines you need to follow:
Please fill out either the individual or corporate Contributor License Agreement
(CLA) with Google. JPEG XL Project is an an effort by multiple individuals and
companies, including the initial contributors Cloudinary and Google, but Google
is the legal entity in charge of receiving these CLA and relicensing this
software:
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA](https://code.google.com/legal/individual-cla-v1.0.html).
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate
CLA](https://code.google.com/legal/corporate-cla-v1.0.html).
Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able
to accept your pull requests.
***NOTE***: Only original source code from you and other people that have signed
the CLA can be accepted into the main repository.
### License
Contributions are licensed under the project's [LICENSE](LICENSE). Each new
file must include the following header when possible, with comment style adapted
to the language as needed:
```
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
```
### Code Reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
### Contribution philosophy
* Prefer small changes, even if they don't implement a complete feature. Small
changes are easier to review and can be submitted faster. Think about what's
the smallest unit you can send that makes sense to review and submit in
isolation. For example, new modules that are not yet used by the tools but
have their own unittests are ok. If you have unrelated changes that
you discovered while working on something else, please send them in a
different Pull Request. If your are refactoring code and changing
functionality try to send the refactor first without any change in
functionality. Reviewers may ask you to split a Pull Request and it is
easier to create a smaller change from the beginning.
* Describe your commits. Add a meaningful description to your commit message, explain what you are changing if it is not trivially obvious, but more importantly explain *why* you are making those changes. For example "Fix
build" is not a good commit message, describe what build and if it makes sense
why is this fixing it or why was it failing without this. It is very likely
that people far in the future without any context you have right now will be
looking at your commit trying to figure out why was the change introduced. If
related to an issue in this or another repository include a link to it.
* Code Style: We follow the [Google C++ Coding
Style](https://google.github.io/styleguide/cppguide.html). A
[clang-format](https://clang.llvm.org/docs/ClangFormat.html) configuration
file is available to automatically format your code, you can invoke it with
the `./ci.sh lint` helper tool.
* Testing: Test your change and explain in the commit message *how* your
commit was tested. For example adding unittests or in some cases just testing
with the existing ones is enough. In any case, mention what testing was
performed so reviewers can evaluate whether that's enough testing. In many
cases, testing that the Continuous Integration workflow passes is enough.
* Make one commit per Pull Request / review, unless there's a good reason not
to. If you have multiple changes send multiple Pull Requests and each one can
have its own review.
* When addressing comments from reviewers prefer to squash or fixup your
edits and force-push your commit. When merging changes into the repository we
don't want to include the history of code review back and forth changes or
typos. Reviewers can click on the "force-pushed" automatic comment on a Pull
Request to see the changes between versions. We use "Rebase and merge" policy
to keep a linear git history which is easier to reason about.
* Your change must pass the build and test workflows. There's a `ci.sh` script
to help building and testing these configurations. See [building and
testing](doc/building_and_testing.md) for more details.
### Contributing checklist.
* Sign the CLA (only needed once per user, see above).
* AUTHORS: If this is your first contribution, add your name or your
company name to the [AUTHORS](AUTHORS) file for copyright tracking purposes.
* Style guide. Check `./ci.sh lint`.
* Meaningful commit description: What and *why*, links to issues, testing
procedure.
* Squashed multiple edits into a single commit.
* Upload your changes to your fork and [create a Pull
Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
# Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).

23
third-party/libjxl/libjxl/CONTRIBUTORS vendored Normal file
View File

@ -0,0 +1,23 @@
# This files lists individuals who made significant contributions to the JPEG XL
# code base, such as design, adding features, performing experiments, ...
# Small changes such as a small bugfix or fixing spelling errors are not
# included. If you'd like to be included in this file thanks to a significant
# contribution, feel free to send a pull request changing this file.
Alex Deymo
Alexander Rhatushnyak
Evgenii Kliuchnikov
Iulia-Maria Comșa
Jan Wassenberg
Jon Sneyers
Jyrki Alakuijala
Krzysztof Potempa
Lode Vandevenne
Luca Versari
Martin Bruse
Moritz Firsching
Renata Khasanova
Robert Obryk
Sami Boukortt
Sebastian Gomez-Gonzalez
Thomas Fischbacher
Zoltan Szabadka

27
third-party/libjxl/libjxl/LICENSE vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) the JPEG XL Project Authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
third-party/libjxl/libjxl/PATENTS vendored Normal file
View File

@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the JPEG XL project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of JPEG XL, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of JPEG XL. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of JPEG XL or any code incorporated within this
implementation of JPEG XL constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of JPEG XL
shall terminate as of the date such litigation is filed.

133
third-party/libjxl/libjxl/README.md vendored Normal file
View File

@ -0,0 +1,133 @@
# JPEG XL reference implementation
[![Build/Test](https://github.com/libjxl/libjxl/actions/workflows/build_test.yml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/build_test.yml)
[![Build/Test Cross](https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml)
[![Conformance](https://github.com/libjxl/libjxl/actions/workflows/conformance.yml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/conformance.yml)
[![CIFuzz](https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml)
[![Releases](https://github.com/libjxl/libjxl/actions/workflows/release.yaml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/release.yaml)
[![Doc](https://readthedocs.org/projects/libjxl/badge/?version=latest)](
https://libjxl.readthedocs.io/en/latest/?badge=latest)
[![codecov](https://codecov.io/gh/libjxl/libjxl/branch/main/graph/badge.svg)](
https://codecov.io/gh/libjxl/libjxl)
<img src="doc/jxl.svg" width="100" align="right" alt="JXL logo">
This repository contains a reference implementation of JPEG XL (encoder and
decoder), called `libjxl`. This software library is
[used by many applications that support JPEG XL](doc/software_support.md).
JPEG XL was standardized in 2022 as [ISO/IEC 18181](https://jpeg.org/jpegxl/workplan.html).
The [core codestream](doc/format_overview.md#codestream-features) is specified in 18181-1,
the [file format](doc/format_overview.md#file-format-features) in 18181-2.
[Decoder conformance](https://github.com/libjxl/conformance) is defined in 18181-3,
and 18181-4 is the [reference software](https://github.com/libjxl/libjxl).
The library API, command line options, and tools in this repository are subject
to change, however files encoded with `cjxl` conform to the JPEG XL specification
and can be decoded with current and future `djxl` decoders or the `libjxl` decoding library.
## Installation
In most Linux distributions, installing `libjxl` is just a matter of using the package management system.
For example in Debian-based distributions: `apt install libjxl-tools` will install `cjxl` and `djxl`
and other tools like `benchmark_xl` are available in the package `libjxl-devtools`.
On MacOS, you can use [Homebrew](https://brew.sh/): `brew install jpeg-xl`.
[![libjxl packaging status](https://repology.org/badge/vertical-allrepos/libjxl.svg?exclude_unsupported=1&columns=3&exclude_sources=modules,site&header=libjxl%20packaging%20status)](https://repology.org/project/libjxl/versions)
From the [releases page](https://github.com/libjxl/libjxl/releases/) the following can be downloaded:
- Windows binaries
- Debian and Ubuntu .deb packages
Of course you can also [build libjxl from sources](BUILDING.md).
## Usage
To encode a source image to JPEG XL with default settings:
```bash
cjxl input.png output.jxl
```
The desired visual fidelity can be selected using the `--distance` parameter
(in units of just-noticeable difference, where 0 is lossless and the most useful lossy range is 0.5 .. 3.0),
or using `--quality` (on a scale from 0 to 100, roughly matching libjpeg).
The [encode effort](doc/encode_effort.md) can be selected using the `--effort` parameter.
For more settings run `cjxl --help` or for a full list of options
run `cjxl -v -v --help`.
To decode a JPEG XL file run:
```bash
djxl input.jxl output.png
```
When possible `cjxl`/`djxl` are able to read/write the following
image formats: .exr, .gif, .jpeg/.jpg, .pfm, .pgm/.ppm, .pgx, .png.
Specifically for JPEG files, the default `cjxl` behavior is to apply lossless
recompression and the default `djxl` behavior is to reconstruct the original
JPEG file (when the extension of the output file is .jpg).
### Benchmarking
For speed benchmarks on single images in single or multi-threaded decoding
`djxl` can print decoding speed information. See `djxl --help` for details
on the decoding options and note that the output image is optional for
benchmarking purposes.
For more comprehensive benchmarking options, see the
[benchmarking guide](doc/benchmarking.md).
### Library API
Besides the `libjxl` library [API documentation](https://libjxl.readthedocs.io/en/latest/),
there are [example applications](examples/) and [plugins](plugins/) that can be used as a reference or
starting point for developers who wish to integrate `libjxl` in their project.
## License
This software is available under a 3-clause BSD license which can be found in
the [LICENSE](LICENSE) file, with an "Additional IP Rights Grant" as outlined in
the [PATENTS](PATENTS) file.
Please note that the PATENTS file only mentions Google since Google is the legal
entity receiving the Contributor License Agreements (CLA) from all contributors
to the JPEG XL Project, including the initial main contributors to the JPEG XL
format: Cloudinary and Google.
## Additional documentation
### Codec description
* [JPEG XL Format Overview](doc/format_overview.md)
* [Introductory paper](https://www.spiedigitallibrary.org/proceedings/Download?fullDOI=10.1117%2F12.2529237) (open-access)
* [XL Overview](doc/xl_overview.md) - a brief introduction to the source code modules
* [JPEG XL white paper](https://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf)
* [JPEG XL official website](https://jpeg.org/jpegxl)
* [JPEG XL community website](https://jpegxl.info)
### Development process
* [More information on testing/build options](doc/building_and_testing.md)
* [Git guide for JPEG XL](doc/developing_in_github.md) - for developers
* [Fuzzing](doc/fuzzing.md) - for developers
* [Building Web Assembly artifacts](doc/building_wasm.md)
* [Test coverage on Codecov.io](https://app.codecov.io/gh/libjxl/libjxl) - for
developers
* [libjxl documentation on readthedocs.io](https://libjxl.readthedocs.io/)
### Contact
If you encounter a bug or other issue with the software, please open an Issue here.
There is a [subreddit about JPEG XL](https://www.reddit.com/r/jpegxl/), and
informal chatting with developers and early adopters of `libjxl` can be done on the
[JPEG XL Discord server](https://discord.gg/DqkQgDRTFu).

73
third-party/libjxl/libjxl/SECURITY.md vendored Normal file
View File

@ -0,0 +1,73 @@
# Security and Vulnerability Policy for libjxl
## TL;DR:
CPE prefix: `cpe:2.3:a:libjxl_project:libjxl`
To report a security issue, please email libjxl-security@google.com.
Include in your email a description of the issue, the steps you took to create
the issue, affected versions, and if known, mitigations for the issue. Our
vulnerability management team will acknowledge receiving your email within 3
working days.
This project follows a 90 day disclosure timeline.
For all other bugs, where there are no security implications about disclosing
the unpatched bug, open a [new issue](https://github.com/libjxl/libjxl/issues)
checking first for existing similar issues. If in doubt about the security
impact of a bug you discovered, email first.
## Policy overview
libjxl's Security Policy is based on the [Google Open Source program
guidelines](https://github.com/google/oss-vulnerability-guide) for coordinated
vulnerability disclosure.
Early versions of `libjxl` had a different security policy that didn't provide
security and vulnerability disclosure support. Versions up to and including
0.3.7 are not covered and won't receive any security advisory.
Only released versions, starting from version 0.5, are covered by this policy.
Development branches, arbitrary commits from `main` branch or even releases with
backported features externally patched on top are not covered. Only those
versions with a release tag in `libjxl`'s repository are covered, starting from
version 0.5.
## What's a "Security bug"
A security bug is a bug that can potentially be exploited to let an attacker
gain unauthorized access or privileges such as disclosing information or
arbitrary code execution. Not all fuzzer-found bugs and not all assert()
failures are considered security bugs in libjxl. For a detailed explanation and
examples see our [Security Vulnerabilities Playbook](doc/vuln_playbook.md).
## What to expect
To report a security issue, please email libjxl-security@google.com with all the
details about the bug you encountered.
* Include a description of the issue, steps to reproduce, etc. Compiler
versions, flags, exact version used and even CPU are often relevant given our
usage of SIMD and run-time dispatch of SIMD instructions.
* A member of our security team will reply to you within 3 business days. Note
that business days are different in different countries.
* We will evaluate the issue and we may require more input from your side to
reproduce it.
* If the issue fits in the description of a security bug, we will issue a
CVE, publish a fix and make a new minor or patch release with it. There is
a maximum of 90 day disclosure timeline, we ask you to not publish the
details before the 90 day deadline or the release date (whichever comes
first).
* In the case that we publish a CVE we will credit the external researcher who
reported the issue. When reporting security issues please let us know if you
need to include specific information while doing so, like for example a
company affiliation.
Our security team follows the [Security Vulnerabilities
Playbook](doc/vuln_playbook.md). For more details about the process and policies
please take a look at it.

768
third-party/libjxl/libjxl/WORKSPACE vendored Normal file
View File

@ -0,0 +1,768 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository")
http_archive(
name = "bazel_skylib",
sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
],
)
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()
local_repository(
name = "highway",
path = "third_party/highway",
)
local_repository(
name = "brotli",
path = "third_party/brotli",
)
new_local_repository(
name = "googletest",
build_file = "third_party/googletest/BUILD.bazel",
path = "third_party/googletest",
)
new_local_repository(
name = "skcms",
build_file_content = """
cc_library(
name = "skcms",
srcs = [
"skcms.cc",
"skcms_internal.h",
"src/Transform_inl.h",
],
hdrs = ["skcms.h"],
visibility = ["//visibility:public"],
)
""",
path = "third_party/skcms",
)
new_git_repository(
name = "zlib",
build_file_content = """
cc_library(
name = "zlib",
defines = ["HAVE_UNISTD_H"],
srcs = [
"adler32.c",
"compress.c",
"crc32.c",
"crc32.h",
"deflate.c",
"deflate.h",
"gzclose.c",
"gzguts.h",
"gzlib.c",
"gzread.c",
"gzwrite.c",
"infback.c",
"inffast.c",
"inffast.h",
"inffixed.h",
"inflate.c",
"inflate.h",
"inftrees.c",
"inftrees.h",
"trees.c",
"trees.h",
"uncompr.c",
"zconf.h",
"zutil.c",
"zutil.h",
],
hdrs = ["zlib.h"],
includes = ["."],
visibility = ["//visibility:public"],
)
""",
remote = "https://github.com/madler/zlib",
tag = "v1.2.13",
)
new_local_repository(
name = "png",
build_file_content = """
genrule(
name = "pnglibconf",
srcs = ["scripts/pnglibconf.h.prebuilt"],
outs = ["pnglibconf.h"],
cmd = "cp -f $< $@",
)
cc_library(
name = "png",
srcs = [
"png.c",
"pngconf.h",
"pngdebug.h",
"pngerror.c",
"pngget.c",
"pnginfo.h",
":pnglibconf",
"pngmem.c",
"pngpread.c",
"pngpriv.h",
"pngread.c",
"pngrio.c",
"pngrtran.c",
"pngrutil.c",
"pngset.c",
"pngstruct.h",
"pngtrans.c",
"pngwio.c",
"pngwrite.c",
"pngwtran.c",
"pngwutil.c",
],
hdrs = ["png.h"],
includes = ["."],
linkopts = ["-lm"],
visibility = ["//visibility:public"],
deps = ["@zlib//:zlib"],
)
""",
path = "third_party/libpng",
)
new_git_repository(
name = "libjpeg_turbo",
build_file_content = """
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
SUBSTITUTIONS = {
"@BUILD@" : "20230208",
"@CMAKE_PROJECT_NAME@" : "libjpeg-turbo",
"@COPYRIGHT_YEAR@" : "2023",
"@INLINE@" : "__inline__",
"@JPEG_LIB_VERSION@" : "62",
"@LIBJPEG_TURBO_VERSION_NUMBER@" : "2001091",
"@SIZE_T@" : "8",
"@THREAD_LOCAL@" : "__thread",
"@VERSION@" : "2.1.91",
}
YES_DEFINES = [
"C_ARITH_CODING_SUPPORTED", "D_ARITH_CODING_SUPPORTED",
"HAVE_BUILTIN_CTZL", "MEM_SRCDST_SUPPORTED"
]
NO_DEFINES = [
"WITH_SIMD", "RIGHT_SHIFT_IS_UNSIGNED", "HAVE_INTRIN_H"
]
SUBSTITUTIONS.update({
"#cmakedefine " + key : "#define " + key for key in YES_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine " + key : "// #define " + key for key in NO_DEFINES
})
[
expand_template(
name = "expand_" + src,
template = src + ".in",
out = src,
substitutions = SUBSTITUTIONS,
visibility = ["//visibility:public"],
) for src in ["jconfig.h", "jconfigint.h", "jversion.h"]
]
JPEG16_SOURCES = [
"jccolor.c",
"jcdiffct.c",
"jclossls.c",
"jcmainct.c",
"jcprepct.c",
"jcsample.c",
"jdcolor.c",
"jddiffct.c",
"jdlossls.c",
"jdmainct.c",
"jdmerge.c",
"jdpostct.c",
"jdsample.c",
"jquant1.c",
"jquant2.c",
"jutils.c",
]
JPEG12_SOURCES = JPEG16_SOURCES + [
"jccoefct.c",
"jcdctmgr.c",
"jdcoefct.c",
"jddctmgr.c",
"jfdctfst.c",
"jfdctint.c",
"jidctflt.c",
"jidctfst.c",
"jidctint.c",
"jidctred.c",
]
JPEG_SOURCES = JPEG12_SOURCES + [
"jaricom.c",
"jcapimin.c",
"jcapistd.c",
"jcarith.c",
"jchuff.c",
"jcicc.c",
"jcinit.c",
"jclhuff.c",
"jcmarker.c",
"jcmaster.c",
"jcomapi.c",
"jcparam.c",
"jcphuff.c",
"jdapimin.c",
"jdapistd.c",
"jdarith.c",
"jdatadst.c",
"jdatasrc.c",
"jdhuff.c",
"jdicc.c",
"jdinput.c",
"jdlhuff.c",
"jdmarker.c",
"jdmaster.c",
"jdphuff.c",
"jdtrans.c",
"jerror.c",
"jfdctflt.c",
"jmemmgr.c",
"jmemnobs.c",
]
JPEG_HEADERS = [
"jccolext.c",
"jchuff.h",
"jcmaster.h",
"jconfig.h",
"jconfigint.h",
"jdcoefct.h",
"jdcol565.c",
"jdcolext.c",
"jdct.h",
"jdhuff.h",
"jdmainct.h",
"jdmaster.h",
"jdmerge.h",
"jdmrg565.c",
"jdmrgext.c",
"jdsample.h",
"jerror.h",
"jinclude.h",
"jlossls.h",
"jmemsys.h",
"jmorecfg.h",
"jpeg_nbits_table.h",
"jpegapicomp.h",
"jpegint.h",
"jpeglib.h",
"jsamplecomp.h",
"jsimd.h",
"jsimddct.h",
"jstdhuff.c",
"jversion.h",
]
cc_library(
name = "jpeg16",
srcs = JPEG16_SOURCES,
hdrs = JPEG_HEADERS,
local_defines = ["BITS_IN_JSAMPLE=16"],
visibility = ["//visibility:public"],
)
cc_library(
name = "jpeg12",
srcs = JPEG12_SOURCES,
hdrs = JPEG_HEADERS,
local_defines = ["BITS_IN_JSAMPLE=12"],
visibility = ["//visibility:public"],
)
cc_library(
name = "jpeg",
srcs = JPEG_SOURCES,
hdrs = JPEG_HEADERS,
deps = [":jpeg16", ":jpeg12"],
includes = ["."],
visibility = ["//visibility:public"],
)
exports_files([
"jmorecfg.h",
"jpeglib.h",
])
""",
remote = "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
tag = "2.1.91",
)
http_archive(
name = "gif",
build_file_content = """
cc_library(
name = "gif",
srcs = [
"dgif_lib.c", "egif_lib.c", "gifalloc.c", "gif_err.c", "gif_font.c",
"gif_hash.c", "openbsd-reallocarray.c", "gif_hash.h",
"gif_lib_private.h"
],
hdrs = ["gif_lib.h"],
includes = ["."],
visibility = ["//visibility:public"],
)
""",
sha256 = "31da5562f44c5f15d63340a09a4fd62b48c45620cd302f77a6d9acf0077879bd",
strip_prefix = "giflib-5.2.1",
url = "https://netcologne.dl.sourceforge.net/project/giflib/giflib-5.2.1.tar.gz",
)
new_git_repository(
name = "imath",
build_file_content = """
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
SUBSTITUTIONS = {
"@IMATH_INTERNAL_NAMESPACE@": "Imath_3_1",
"@IMATH_LIB_VERSION@": "3.1.4",
"@IMATH_NAMESPACE_CUSTOM@": "0",
"@IMATH_NAMESPACE@": "Imath",
"@IMATH_PACKAGE_NAME@": "Imath 3.1.4",
"@IMATH_VERSION_MAJOR@": "3",
"@IMATH_VERSION_MINOR@": "1",
"@IMATH_VERSION_PATCH@": "4",
"@IMATH_VERSION@": "3.1.4",
}
YES_DEFINES = [
"IMATH_HALF_USE_LOOKUP_TABLE", "IMATH_ENABLE_API_VISIBILITY",
]
NO_DEFINES = [
"IMATH_HAVE_LARGE_STACK",
]
ONE_DEFINES = [
"IMATH_USE_NOEXCEPT",
]
SUBSTITUTIONS.update({
"#cmakedefine " + key : "#define " + key for key in YES_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine " + key : "// #define " + key for key in NO_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine01 " + key : "#define " + key + " 1" for key in ONE_DEFINES
})
expand_template(
name = "expand_ImathConfig",
template = "config/ImathConfig.h.in",
out = "src/Imath/ImathConfig.h",
substitutions = SUBSTITUTIONS,
)
cc_library(
name = "Imath",
srcs = [
"src/Imath/ImathColorAlgo.cpp",
":src/Imath/ImathConfig.h",
"src/Imath/ImathFun.cpp",
"src/Imath/ImathMatrixAlgo.cpp",
"src/Imath/ImathRandom.cpp",
"src/Imath/half.cpp",
"src/Imath/toFloat.h",
],
hdrs = [
"src/Imath/ImathBox.h",
"src/Imath/ImathBoxAlgo.h",
"src/Imath/ImathColor.h",
"src/Imath/ImathColorAlgo.h",
"src/Imath/ImathEuler.h",
"src/Imath/ImathExport.h",
"src/Imath/ImathForward.h",
"src/Imath/ImathFrame.h",
"src/Imath/ImathFrustum.h",
"src/Imath/ImathFrustumTest.h",
"src/Imath/ImathFun.h",
"src/Imath/ImathGL.h",
"src/Imath/ImathGLU.h",
"src/Imath/ImathInt64.h",
"src/Imath/ImathInterval.h",
"src/Imath/ImathLine.h",
"src/Imath/ImathLineAlgo.h",
"src/Imath/ImathMath.h",
"src/Imath/ImathMatrix.h",
"src/Imath/ImathMatrixAlgo.h",
"src/Imath/ImathNamespace.h",
"src/Imath/ImathPlane.h",
"src/Imath/ImathPlatform.h",
"src/Imath/ImathQuat.h",
"src/Imath/ImathRandom.h",
"src/Imath/ImathRoots.h",
"src/Imath/ImathShear.h",
"src/Imath/ImathSphere.h",
"src/Imath/ImathTypeTraits.h",
"src/Imath/ImathVec.h",
"src/Imath/ImathVecAlgo.h",
"src/Imath/half.h",
"src/Imath/halfFunction.h",
"src/Imath/halfLimits.h",
],
includes = ["src/Imath"],
visibility = ["//visibility:public"],
)
""",
remote = "https://github.com/AcademySoftwareFoundation/imath",
tag = "v3.1.5",
)
new_git_repository(
name = "openexr",
build_file_content = """
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
SUBSTITUTIONS = {
"@IEX_INTERNAL_NAMESPACE@": "Iex_3_0",
"@IEX_NAMESPACE_CUSTOM@": "0",
"@IEX_NAMESPACE@": "Iex",
"@ILMTHREAD_INTERNAL_NAMESPACE@": "IlmThread_3_0",
"@ILMTHREAD_NAMESPACE_CUSTOM@": "0",
"@ILMTHREAD_NAMESPACE@": "IlmThread",
"@OPENEXR_IMF_NAMESPACE@": "Imf",
"@OPENEXR_INTERNAL_IMF_NAMESPACE@": "Imf_3_0",
"@OPENEXR_LIB_VERSION@": "3.0.4",
"@OPENEXR_NAMESPACE_CUSTOM@": "0",
"@OPENEXR_PACKAGE_NAME@": "OpenEXR 3.0.4",
"@OPENEXR_VERSION_EXTRA@": "",
"@OPENEXR_VERSION_MAJOR@": "3",
"@OPENEXR_VERSION_MINOR@": "0",
"@OPENEXR_VERSION_PATCH@": "4",
"@OPENEXR_VERSION@": "3.0.4",
}
YES_DEFINES = [
"OPENEXR_ENABLE_API_VISIBILITY", "OPENEXR_IMF_HAVE_COMPLETE_IOMANIP",
"OPENEXR_HAVE_LARGE_STACK",
]
NO_DEFINES = [
"HAVE_UCONTEXT_H", "IEX_HAVE_CONTROL_REGISTER_SUPPORT",
"IEX_HAVE_SIGCONTEXT_CONTROL_REGISTER_SUPPORT", "OPENEXR_IMF_HAVE_DARWIN",
"OPENEXR_IMF_HAVE_GCC_INLINE_ASM_AVX", "OPENEXR_IMF_HAVE_LINUX_PROCFS",
"OPENEXR_IMF_HAVE_SYSCONF_NPROCESSORS_ONLN",
]
ONE_DEFINES = [
"ILMTHREAD_THREADING_ENABLED",
]
ZERO_DEFINES = [
"ILMTHREAD_HAVE_POSIX_SEMAPHORES",
]
SUBSTITUTIONS.update({
"#cmakedefine " + key : "#define " + key for key in YES_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine " + key : "// #define " + key for key in NO_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine01 " + key : "#define " + key + " 1" for key in ONE_DEFINES
})
SUBSTITUTIONS.update({
"#cmakedefine01 " + key : "#define " + key + " 0" for key in ZERO_DEFINES
})
[
expand_template(
name = "expand_" + item,
template = "cmake/" + item + ".h.in",
out = "src/lib/Iex/" + item + ".h",
substitutions = SUBSTITUTIONS,
) for item in ["IexConfig", "IexConfigInternal"]
]
[
expand_template(
name = "expand_" + item,
template = "cmake/" + item + ".h.in",
out = "src/lib/IlmThread/" + item + ".h",
substitutions = SUBSTITUTIONS,
) for item in ["IlmThreadConfig"]
]
[
expand_template(
name = "expand_" + item,
template = "cmake/" + item + ".h.in",
out = "src/lib/OpenEXR/" + item + ".h",
substitutions = SUBSTITUTIONS,
) for item in ["OpenEXRConfig", "OpenEXRConfigInternal"]
]
cc_library(
name = "Iex",
srcs = [
"src/lib/Iex/IexBaseExc.cpp",
"src/lib/Iex/IexMathFloatExc.cpp",
"src/lib/Iex/IexMathFpu.cpp",
"src/lib/Iex/IexThrowErrnoExc.cpp",
],
hdrs = [
"src/lib/Iex/Iex.h",
"src/lib/Iex/IexBaseExc.h",
":src/lib/Iex/IexConfig.h",
":src/lib/Iex/IexConfigInternal.h",
"src/lib/Iex/IexErrnoExc.h",
"src/lib/Iex/IexExport.h",
"src/lib/Iex/IexForward.h",
"src/lib/Iex/IexMacros.h",
"src/lib/Iex/IexMathExc.h",
"src/lib/Iex/IexMathFloatExc.h",
"src/lib/Iex/IexMathFpu.h",
"src/lib/Iex/IexMathIeeeExc.h",
"src/lib/Iex/IexNamespace.h",
"src/lib/Iex/IexThrowErrnoExc.h",
":src/lib/OpenEXR/OpenEXRConfig.h",
],
includes = [
"src/lib/Iex",
"src/lib/OpenEXR",
],
)
cc_library(
name = "IlmThread",
srcs = [
"src/lib/IlmThread/IlmThread.cpp",
"src/lib/IlmThread/IlmThreadPool.cpp",
"src/lib/IlmThread/IlmThreadSemaphore.cpp",
"src/lib/IlmThread/IlmThreadSemaphoreOSX.cpp",
"src/lib/IlmThread/IlmThreadSemaphorePosix.cpp",
"src/lib/IlmThread/IlmThreadSemaphorePosixCompat.cpp",
"src/lib/IlmThread/IlmThreadSemaphoreWin32.cpp",
],
hdrs = [
"src/lib/IlmThread/IlmThread.h",
":src/lib/IlmThread/IlmThreadConfig.h",
"src/lib/IlmThread/IlmThreadExport.h",
"src/lib/IlmThread/IlmThreadForward.h",
"src/lib/IlmThread/IlmThreadMutex.h",
"src/lib/IlmThread/IlmThreadNamespace.h",
"src/lib/IlmThread/IlmThreadPool.h",
"src/lib/IlmThread/IlmThreadSemaphore.h",
],
includes = ["src/lib/IlmThread"],
deps = [":Iex"],
)
cc_library(
name = "OpenEXR",
srcs = [
"src/lib/OpenEXR/ImfAcesFile.cpp",
"src/lib/OpenEXR/ImfAttribute.cpp",
"src/lib/OpenEXR/ImfB44Compressor.cpp",
"src/lib/OpenEXR/ImfBoxAttribute.cpp",
"src/lib/OpenEXR/ImfCRgbaFile.cpp",
"src/lib/OpenEXR/ImfChannelList.cpp",
"src/lib/OpenEXR/ImfChannelListAttribute.cpp",
"src/lib/OpenEXR/ImfChromaticities.cpp",
"src/lib/OpenEXR/ImfChromaticitiesAttribute.cpp",
"src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp",
"src/lib/OpenEXR/ImfCompressionAttribute.cpp",
"src/lib/OpenEXR/ImfCompressor.cpp",
"src/lib/OpenEXR/ImfConvert.cpp",
"src/lib/OpenEXR/ImfDeepCompositing.cpp",
"src/lib/OpenEXR/ImfDeepFrameBuffer.cpp",
"src/lib/OpenEXR/ImfDeepImageStateAttribute.cpp",
"src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp",
"src/lib/OpenEXR/ImfDeepScanLineInputPart.cpp",
"src/lib/OpenEXR/ImfDeepScanLineOutputFile.cpp",
"src/lib/OpenEXR/ImfDeepScanLineOutputPart.cpp",
"src/lib/OpenEXR/ImfDeepTiledInputFile.cpp",
"src/lib/OpenEXR/ImfDeepTiledInputPart.cpp",
"src/lib/OpenEXR/ImfDeepTiledOutputFile.cpp",
"src/lib/OpenEXR/ImfDeepTiledOutputPart.cpp",
"src/lib/OpenEXR/ImfDoubleAttribute.cpp",
"src/lib/OpenEXR/ImfDwaCompressor.cpp",
"src/lib/OpenEXR/ImfEnvmap.cpp",
"src/lib/OpenEXR/ImfEnvmapAttribute.cpp",
"src/lib/OpenEXR/ImfFastHuf.cpp",
"src/lib/OpenEXR/ImfFloatAttribute.cpp",
"src/lib/OpenEXR/ImfFloatVectorAttribute.cpp",
"src/lib/OpenEXR/ImfFrameBuffer.cpp",
"src/lib/OpenEXR/ImfFramesPerSecond.cpp",
"src/lib/OpenEXR/ImfGenericInputFile.cpp",
"src/lib/OpenEXR/ImfGenericOutputFile.cpp",
"src/lib/OpenEXR/ImfHeader.cpp",
"src/lib/OpenEXR/ImfHuf.cpp",
"src/lib/OpenEXR/ImfIDManifest.cpp",
"src/lib/OpenEXR/ImfIDManifestAttribute.cpp",
"src/lib/OpenEXR/ImfIO.cpp",
"src/lib/OpenEXR/ImfInputFile.cpp",
"src/lib/OpenEXR/ImfInputPart.cpp",
"src/lib/OpenEXR/ImfInputPartData.cpp",
"src/lib/OpenEXR/ImfIntAttribute.cpp",
"src/lib/OpenEXR/ImfKeyCode.cpp",
"src/lib/OpenEXR/ImfKeyCodeAttribute.cpp",
"src/lib/OpenEXR/ImfLineOrderAttribute.cpp",
"src/lib/OpenEXR/ImfLut.cpp",
"src/lib/OpenEXR/ImfMatrixAttribute.cpp",
"src/lib/OpenEXR/ImfMisc.cpp",
"src/lib/OpenEXR/ImfMultiPartInputFile.cpp",
"src/lib/OpenEXR/ImfMultiPartOutputFile.cpp",
"src/lib/OpenEXR/ImfMultiView.cpp",
"src/lib/OpenEXR/ImfOpaqueAttribute.cpp",
"src/lib/OpenEXR/ImfOutputFile.cpp",
"src/lib/OpenEXR/ImfOutputPart.cpp",
"src/lib/OpenEXR/ImfOutputPartData.cpp",
"src/lib/OpenEXR/ImfPartType.cpp",
"src/lib/OpenEXR/ImfPizCompressor.cpp",
"src/lib/OpenEXR/ImfPreviewImage.cpp",
"src/lib/OpenEXR/ImfPreviewImageAttribute.cpp",
"src/lib/OpenEXR/ImfPxr24Compressor.cpp",
"src/lib/OpenEXR/ImfRational.cpp",
"src/lib/OpenEXR/ImfRationalAttribute.cpp",
"src/lib/OpenEXR/ImfRgbaFile.cpp",
"src/lib/OpenEXR/ImfRgbaYca.cpp",
"src/lib/OpenEXR/ImfRle.cpp",
"src/lib/OpenEXR/ImfRleCompressor.cpp",
"src/lib/OpenEXR/ImfScanLineInputFile.cpp",
"src/lib/OpenEXR/ImfStandardAttributes.cpp",
"src/lib/OpenEXR/ImfStdIO.cpp",
"src/lib/OpenEXR/ImfStringAttribute.cpp",
"src/lib/OpenEXR/ImfStringVectorAttribute.cpp",
"src/lib/OpenEXR/ImfSystemSpecific.cpp",
"src/lib/OpenEXR/ImfTestFile.cpp",
"src/lib/OpenEXR/ImfThreading.cpp",
"src/lib/OpenEXR/ImfTileDescriptionAttribute.cpp",
"src/lib/OpenEXR/ImfTileOffsets.cpp",
"src/lib/OpenEXR/ImfTiledInputFile.cpp",
"src/lib/OpenEXR/ImfTiledInputPart.cpp",
"src/lib/OpenEXR/ImfTiledMisc.cpp",
"src/lib/OpenEXR/ImfTiledOutputFile.cpp",
"src/lib/OpenEXR/ImfTiledOutputPart.cpp",
"src/lib/OpenEXR/ImfTiledRgbaFile.cpp",
"src/lib/OpenEXR/ImfTimeCode.cpp",
"src/lib/OpenEXR/ImfTimeCodeAttribute.cpp",
"src/lib/OpenEXR/ImfVecAttribute.cpp",
"src/lib/OpenEXR/ImfVersion.cpp",
"src/lib/OpenEXR/ImfWav.cpp",
"src/lib/OpenEXR/ImfZip.cpp",
"src/lib/OpenEXR/ImfZipCompressor.cpp",
"src/lib/OpenEXR/b44ExpLogTable.h",
"src/lib/OpenEXR/dwaLookups.h",
],
hdrs = [
":src/lib/Iex/IexConfig.h",
":src/lib/Iex/IexConfigInternal.h",
":src/lib/IlmThread/IlmThreadConfig.h",
"src/lib/OpenEXR/ImfAcesFile.h",
"src/lib/OpenEXR/ImfArray.h",
"src/lib/OpenEXR/ImfAttribute.h",
"src/lib/OpenEXR/ImfAutoArray.h",
"src/lib/OpenEXR/ImfB44Compressor.h",
"src/lib/OpenEXR/ImfBoxAttribute.h",
"src/lib/OpenEXR/ImfCRgbaFile.h",
"src/lib/OpenEXR/ImfChannelList.h",
"src/lib/OpenEXR/ImfChannelListAttribute.h",
"src/lib/OpenEXR/ImfCheckedArithmetic.h",
"src/lib/OpenEXR/ImfChromaticities.h",
"src/lib/OpenEXR/ImfChromaticitiesAttribute.h",
"src/lib/OpenEXR/ImfCompositeDeepScanLine.h",
"src/lib/OpenEXR/ImfCompression.h",
"src/lib/OpenEXR/ImfCompressionAttribute.h",
"src/lib/OpenEXR/ImfCompressor.h",
"src/lib/OpenEXR/ImfConvert.h",
"src/lib/OpenEXR/ImfDeepCompositing.h",
"src/lib/OpenEXR/ImfDeepFrameBuffer.h",
"src/lib/OpenEXR/ImfDeepImageState.h",
"src/lib/OpenEXR/ImfDeepImageStateAttribute.h",
"src/lib/OpenEXR/ImfDeepScanLineInputFile.h",
"src/lib/OpenEXR/ImfDeepScanLineInputPart.h",
"src/lib/OpenEXR/ImfDeepScanLineOutputFile.h",
"src/lib/OpenEXR/ImfDeepScanLineOutputPart.h",
"src/lib/OpenEXR/ImfDeepTiledInputFile.h",
"src/lib/OpenEXR/ImfDeepTiledInputPart.h",
"src/lib/OpenEXR/ImfDeepTiledOutputFile.h",
"src/lib/OpenEXR/ImfDeepTiledOutputPart.h",
"src/lib/OpenEXR/ImfDoubleAttribute.h",
"src/lib/OpenEXR/ImfDwaCompressor.h",
"src/lib/OpenEXR/ImfDwaCompressorSimd.h",
"src/lib/OpenEXR/ImfEnvmap.h",
"src/lib/OpenEXR/ImfEnvmapAttribute.h",
"src/lib/OpenEXR/ImfExport.h",
"src/lib/OpenEXR/ImfFastHuf.h",
"src/lib/OpenEXR/ImfFloatAttribute.h",
"src/lib/OpenEXR/ImfFloatVectorAttribute.h",
"src/lib/OpenEXR/ImfForward.h",
"src/lib/OpenEXR/ImfFrameBuffer.h",
"src/lib/OpenEXR/ImfFramesPerSecond.h",
"src/lib/OpenEXR/ImfGenericInputFile.h",
"src/lib/OpenEXR/ImfGenericOutputFile.h",
"src/lib/OpenEXR/ImfHeader.h",
"src/lib/OpenEXR/ImfHuf.h",
"src/lib/OpenEXR/ImfIDManifest.h",
"src/lib/OpenEXR/ImfIDManifestAttribute.h",
"src/lib/OpenEXR/ImfIO.h",
"src/lib/OpenEXR/ImfInputFile.h",
"src/lib/OpenEXR/ImfInputPart.h",
"src/lib/OpenEXR/ImfInputPartData.h",
"src/lib/OpenEXR/ImfInputStreamMutex.h",
"src/lib/OpenEXR/ImfInt64.h",
"src/lib/OpenEXR/ImfIntAttribute.h",
"src/lib/OpenEXR/ImfKeyCode.h",
"src/lib/OpenEXR/ImfKeyCodeAttribute.h",
"src/lib/OpenEXR/ImfLineOrder.h",
"src/lib/OpenEXR/ImfLineOrderAttribute.h",
"src/lib/OpenEXR/ImfLut.h",
"src/lib/OpenEXR/ImfMatrixAttribute.h",
"src/lib/OpenEXR/ImfMisc.h",
"src/lib/OpenEXR/ImfMultiPartInputFile.h",
"src/lib/OpenEXR/ImfMultiPartOutputFile.h",
"src/lib/OpenEXR/ImfMultiView.h",
"src/lib/OpenEXR/ImfName.h",
"src/lib/OpenEXR/ImfNamespace.h",
"src/lib/OpenEXR/ImfOpaqueAttribute.h",
"src/lib/OpenEXR/ImfOptimizedPixelReading.h",
"src/lib/OpenEXR/ImfOutputFile.h",
"src/lib/OpenEXR/ImfOutputPart.h",
"src/lib/OpenEXR/ImfOutputPartData.h",
"src/lib/OpenEXR/ImfOutputStreamMutex.h",
"src/lib/OpenEXR/ImfPartHelper.h",
"src/lib/OpenEXR/ImfPartType.h",
"src/lib/OpenEXR/ImfPixelType.h",
"src/lib/OpenEXR/ImfPizCompressor.h",
"src/lib/OpenEXR/ImfPreviewImage.h",
"src/lib/OpenEXR/ImfPreviewImageAttribute.h",
"src/lib/OpenEXR/ImfPxr24Compressor.h",
"src/lib/OpenEXR/ImfRational.h",
"src/lib/OpenEXR/ImfRationalAttribute.h",
"src/lib/OpenEXR/ImfRgba.h",
"src/lib/OpenEXR/ImfRgbaFile.h",
"src/lib/OpenEXR/ImfRgbaYca.h",
"src/lib/OpenEXR/ImfRle.h",
"src/lib/OpenEXR/ImfRleCompressor.h",
"src/lib/OpenEXR/ImfScanLineInputFile.h",
"src/lib/OpenEXR/ImfSimd.h",
"src/lib/OpenEXR/ImfStandardAttributes.h",
"src/lib/OpenEXR/ImfStdIO.h",
"src/lib/OpenEXR/ImfStringAttribute.h",
"src/lib/OpenEXR/ImfStringVectorAttribute.h",
"src/lib/OpenEXR/ImfSystemSpecific.h",
"src/lib/OpenEXR/ImfTestFile.h",
"src/lib/OpenEXR/ImfThreading.h",
"src/lib/OpenEXR/ImfTileDescription.h",
"src/lib/OpenEXR/ImfTileDescriptionAttribute.h",
"src/lib/OpenEXR/ImfTileOffsets.h",
"src/lib/OpenEXR/ImfTiledInputFile.h",
"src/lib/OpenEXR/ImfTiledInputPart.h",
"src/lib/OpenEXR/ImfTiledMisc.h",
"src/lib/OpenEXR/ImfTiledOutputFile.h",
"src/lib/OpenEXR/ImfTiledOutputPart.h",
"src/lib/OpenEXR/ImfTiledRgbaFile.h",
"src/lib/OpenEXR/ImfTimeCode.h",
"src/lib/OpenEXR/ImfTimeCodeAttribute.h",
"src/lib/OpenEXR/ImfVecAttribute.h",
"src/lib/OpenEXR/ImfVersion.h",
"src/lib/OpenEXR/ImfWav.h",
"src/lib/OpenEXR/ImfXdr.h",
"src/lib/OpenEXR/ImfZip.h",
"src/lib/OpenEXR/ImfZipCompressor.h",
":src/lib/OpenEXR/OpenEXRConfig.h",
":src/lib/OpenEXR/OpenEXRConfigInternal.h",
],
includes = ["src/lib/OpenEXR"],
deps = [
":IlmThread",
"@imath//:Imath",
"@zlib//:zlib",
],
visibility = ["//visibility:public"],
)
""",
remote = "https://github.com/AcademySoftwareFoundation/openexr",
tag = "v3.1.5",
)

317
third-party/libjxl/libjxl/bash_test.sh vendored Executable file
View File

@ -0,0 +1,317 @@
#!/bin/bash
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Tests implemented in bash. These typically will run checks about the source
# code rather than the compiled one.
MYDIR=$(dirname $(realpath "$0"))
set -u
test_includes() {
local ret=0
local f
for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do
if [ ! -e "$f" ]; then
continue
fi
# Check that the full paths to the public headers are not used, since users
# of the library will include the library as: #include "jxl/foobar.h".
if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then
echo "Don't add \"include/\" to the include path of public headers." >&2
ret=1
fi
if [[ "${f#third_party/}" == "$f" ]]; then
# $f is not in third_party/
# Check that local files don't use the full path to third_party/
# directory since the installed versions will not have that path.
# Add an exception for third_party/dirent.h.
if grep -v -F 'third_party/dirent.h' "$f" | \
grep -i -H -n -E '#include\s*[<"]third_party/' >&2 &&
[[ $ret -eq 0 ]]; then
cat >&2 <<EOF
$f: Don't add third_party/ to the include path of third_party projects. This \
makes it harder to use installed system libraries instead of the third_party/ \
ones.
EOF
ret=1
fi
fi
done
return ${ret}
}
test_include_collision() {
local ret=0
local f
for f in $(git ls-files | grep -E '^lib/include/'); do
if [ ! -e "$f" ]; then
continue
fi
local base=${f#lib/include/}
if [[ -e "lib/${base}" ]]; then
echo "$f: Name collision, both $f and lib/${base} exist." >&2
ret=1
fi
done
return ${ret}
}
test_copyright() {
local ret=0
local f
for f in $(
git ls-files | grep -E \
'(Dockerfile.*|\.c|\.cc|\.cpp|\.gni|\.h|\.java|\.sh|\.m|\.py|\.ui|\.yml)$'); do
if [ ! -e "$f" ]; then
continue
fi
if [[ "${f#third_party/}" == "$f" ]]; then
# $f is not in third_party/
if ! head -n 10 "$f" |
grep -F 'Copyright (c) the JPEG XL Project Authors.' >/dev/null ; then
echo "$f: Missing Copyright blob near the top of the file." >&2
ret=1
fi
if ! head -n 10 "$f" |
grep -F 'Use of this source code is governed by a BSD-style' \
>/dev/null ; then
echo "$f: Missing License blob near the top of the file." >&2
ret=1
fi
fi
done
return ${ret}
}
# Check that we don't use "%zu" or "%zd" in format string for size_t.
test_printf_size_t() {
local ret=0
if grep -n -E '%[0-9]*z[udx]' \
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$'); then
echo "Don't use '%zu' or '%zd' in a format string, instead use " \
"'%\" PRIuS \"' or '%\" PRIdS \"'." >&2
ret=1
fi
if grep -n -E 'gtest\.h' \
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /testing.h); then
echo "Don't include gtest directly, instead include 'testing.h'. " >&2
ret=1
fi
if grep -n -E 'gmock\.h' \
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /testing.h); then
echo "Don't include gmock directly, instead include 'testing.h'. " >&2
ret=1
fi
local f
for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' |
cut -f 1 -d : | uniq); do
if [ ! -e "$f" ]; then
continue
fi
if ! grep -F printf_macros.h "$f" >/dev/null; then
echo "$f: Add lib/jxl/base/printf_macros.h for PRI.S, or use other " \
"types for code outside lib/jxl library." >&2
ret=1
fi
done
for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|testing\.h)' |
xargs grep -n 'PRI[udx]S'); do
# Having PRIuS / PRIdS in a header file means that printf_macros.h may
# be included before a system header, in particular before gtest headers.
# those may re-define PRIuS unconditionally causing a compile error.
echo "$f: Don't use PRI.S in header files. Sorry."
ret=1
done
return ${ret}
}
# Check that "dec_" code doesn't depend on "enc_" headers.
test_dec_enc_deps() {
local ret=0
local f
for f in $(git ls-files | grep -E '/dec_'); do
if [ ! -e "$f" ]; then
continue
fi
if [[ "${f#third_party/}" == "$f" ]]; then
# $f is not in third_party/
if grep -n -H -E "#include.*/enc_" "$f" >&2; then
echo "$f: Don't include \"enc_*\" files from \"dec_*\" files." >&2
ret=1
fi
fi
done
return ${ret}
}
# Check for git merge conflict markers.
test_merge_conflict() {
local ret=0
TEXT_FILES='(\.cc|\.cpp|\.h|\.sh|\.m|\.py|\.md|\.txt|\.cmake)$'
for f in $(git ls-files | grep -E "${TEXT_FILES}"); do
if [ ! -e "$f" ]; then
continue
fi
if grep -E '^<<<<<<< ' "$f"; then
echo "$f: Found git merge conflict marker. Please resolve." >&2
ret=1
fi
done
return ${ret}
}
# Check that the library and the package have the same version. This prevents
# accidentally having them out of sync.
get_version() {
local varname=$1
local line=$(grep -F "set(${varname} " lib/CMakeLists.txt | head -n 1)
[[ -n "${line}" ]]
line="${line#set(${varname} }"
line="${line%)}"
echo "${line}"
}
test_version() {
local major=$(get_version JPEGXL_MAJOR_VERSION)
local minor=$(get_version JPEGXL_MINOR_VERSION)
local patch=$(get_version JPEGXL_PATCH_VERSION)
# Check that the version is not empty
if [[ -z "${major}${minor}${patch}" ]]; then
echo "Couldn't parse version from CMakeLists.txt" >&2
return 1
fi
local pkg_version=$(head -n 1 debian/changelog)
# Get only the part between the first "jpeg-xl (" and the following ")".
pkg_version="${pkg_version#jpeg-xl (}"
pkg_version="${pkg_version%%)*}"
if [[ -z "${pkg_version}" ]]; then
echo "Couldn't parse version from debian package" >&2
return 1
fi
local lib_version="${major}.${minor}.${patch}"
lib_version="${lib_version%.0}"
if [[ "${pkg_version}" != "${lib_version}"* ]]; then
echo "Debian package version (${pkg_version}) doesn't match library" \
"version (${lib_version})." >&2
return 1
fi
return 0
}
# Check that the SHA versions in deps.sh matches the git submodules.
test_deps_version() {
while IFS= read -r line; do
if [[ "${line:0:10}" != "[submodule" ]]; then
continue
fi
line="${line#[submodule \"}"
line="${line%\"]}"
local varname=$(tr '[:lower:]' '[:upper:]' <<< "${line}")
varname="${varname/\//_}"
if ! grep -F "${varname}=" deps.sh >/dev/null; then
# Ignoring submodule not in deps.sh
continue
fi
local deps_sha=$(grep -F "${varname}=" deps.sh | cut -f 2 -d '"')
[[ -n "${deps_sha}" ]]
local git_sha=$(git ls-tree -r HEAD "${line}" | cut -f 1 | cut -f 3 -d ' ')
if [[ "${deps_sha}" != "${git_sha}" ]]; then
cat >&2 <<EOF
deps.sh: SHA for project ${line} is at ${deps_sha} but the git submodule is at
${git_sha}. Please update deps.sh
If you did not intend to change the submodule's SHA value, it is possible that
you accidentally included this change in your commit after a rebase or checkout
without running "git submodule --init". To revert the submodule change run from
the top checkout directory:
git -C ${line} checkout ${deps_sha}
git commit --amend ${line}
EOF
return 1
fi
done < .gitmodules
}
# Make sure that all the Fields objects are fuzzed directly.
test_fuzz_fields() {
local ret=0
# List all the classes of the form "ClassName : public Fields".
# This doesn't catch class names that are too long to fit.
local field_classes=$( git ls-files |
grep -E '\.(cc|h)' | grep -v 'test\.cc$' |
xargs grep -h -o -E '\b[^ ]+ : public Fields' | cut -f 1 -d ' ')
local classname
for classname in ${field_classes}; do
if [ ! -e "$classname" ]; then
continue
fi
if ! grep -E "\\b${classname}\\b" tools/fields_fuzzer.cc >/dev/null; then
cat >&2 <<EOF
tools/fields_fuzzer.cc: Class ${classname} not found in the fields_fuzzer.
EOF
ret=1
fi
done
return $ret
}
# Test that we don't use %n in C++ code to avoid using it in printf and scanf.
# This test is not very precise but in cases where "module n" is needed we would
# normally have "% n" instead of "%n". Using %n is not allowed in Android 10+.
test_percent_n() {
local ret=0
local f
for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do
if [ ! -e "$f" ]; then
continue
fi
if grep -i -H -n -E '%h*n' "$f" >&2; then
echo "Don't use \"%n\"." >&2
ret=1
fi
done
return ${ret}
}
main() {
local ret=0
cd "${MYDIR}"
if ! git rev-parse >/dev/null 2>/dev/null; then
echo "Not a git checkout, skipping bash_test"
return 0
fi
IFS=$'\n'
for f in $(declare -F); do
local test_name=$(echo "$f" | cut -f 3 -d ' ')
# Runs all the local bash functions that start with "test_".
if [[ "${test_name}" == test_* ]]; then
echo "Test ${test_name}: Start"
if ${test_name}; then
echo "Test ${test_name}: PASS"
else
echo "Test ${test_name}: FAIL"
ret=1
fi
fi
done
return ${ret}
}
main "$@"

1552
third-party/libjxl/libjxl/ci.sh vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
# Original issue:
# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
#
# For reference:
# * https://gcc.gnu.org/wiki/Atomic/GCCMM
#
# riscv64 specific:
# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
#
# ATOMICS_FOUND - system has c++ atomics
# ATOMICS_LIBRARIES - libraries needed to use c++ atomics
include(CheckCXXSourceCompiles)
# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
# to convert smaller atomics to those larger ones via masking and
# shifting like LLVM, but its a known bug that it does not. This means
# anything that wants to use atomics on 1-byte or 2-byte types needs
# -latomic, but not 4-byte or 8-byte (though it does no harm).
set(atomic_code
"
#include <atomic>
#include <cstdint>
std::atomic<uint8_t> n8 (0); // riscv64
std::atomic<uint64_t> n64 (0); // armel, mipsel, powerpc
int main() {
++n8;
++n64;
return 0;
}")
check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS)
if(ATOMICS_LOCK_FREE_INSTRUCTIONS)
set(ATOMICS_FOUND TRUE)
set(ATOMICS_LIBRARIES)
else()
set(CMAKE_REQUIRED_LIBRARIES "-latomic")
check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY)
set(CMAKE_REQUIRED_LIBRARIES)
if(ATOMICS_IN_LIBRARY)
set(ATOMICS_LIBRARY atomic)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY})
unset(ATOMICS_LIBRARY)
else()
if(Atomics_FIND_REQUIRED)
message(FATAL_ERROR "Neither lock free instructions nor -latomic found.")
endif()
endif()
endif()
unset(atomic_code)

View File

@ -0,0 +1,75 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
set(brlibs brotlicommon brotlienc brotlidec)
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
foreach(brlib IN ITEMS ${brlibs})
string(TOUPPER "${brlib}" BRPREFIX)
pkg_check_modules("PC_${BRPREFIX}" lib${brlib})
endforeach()
endif()
find_path(BROTLI_INCLUDE_DIR
NAMES brotli/decode.h
HINTS ${PC_BROTLICOMMON_INCLUDEDIR} ${PC_BROTLICOMMON_INCLUDE_DIRS}
)
foreach(brlib IN ITEMS ${brlibs})
string(TOUPPER "${brlib}" BRPREFIX)
find_library(${BRPREFIX}_LIBRARY
NAMES ${${BRPREFIX}_NAMES} ${brlib}
HINTS ${PC_${BRPREFIX}_LIBDIR} ${PC_${BRPREFIX}_LIBRARY_DIRS}
)
if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib})
if(CMAKE_VERSION VERSION_LESS "3.13.5")
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
set_property(TARGET ${brlib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY})
set_property(TARGET ${brlib} PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER})
else()
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
target_include_directories(${brlib}
INTERFACE ${BROTLI_INCLUDE_DIR})
target_link_libraries(${brlib}
INTERFACE ${${BRPREFIX}_LIBRARY})
target_link_options(${brlib}
INTERFACE ${PC_${BRPREFIX}_LDFLAGS_OTHER})
target_compile_options(${brlib}
INTERFACE ${PC_${BRPREFIX}_CFLAGS_OTHER})
endif()
endif()
endforeach()
if (BROTLICOMMON_FOUND AND BROTLIENC_FOUND AND BROTLIDEC_FOUND)
set(Brotli_FOUND ON)
else ()
set(Brotli_FOUND OFF)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Brotli
FOUND_VAR Brotli_FOUND
REQUIRED_VARS
BROTLI_INCLUDE_DIR
BROTLICOMMON_LIBRARY
BROTLIENC_LIBRARY
BROTLIDEC_LIBRARY
VERSION_VAR Brotli_VERSION
)
mark_as_advanced(
BROTLI_INCLUDE_DIR
BROTLICOMMON_LIBRARY
BROTLIENC_LIBRARY
BROTLIDEC_LIBRARY
)
if (Brotli_FOUND)
set(Brotli_LIBRARIES ${BROTLICOMMON_LIBRARY} ${BROTLIENC_LIBRARY} ${BROTLIDEC_LIBRARY})
set(Brotli_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR})
endif()

View File

@ -0,0 +1,66 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(PC_HWY QUIET libhwy)
set(HWY_VERSION ${PC_HWY_VERSION})
endif ()
find_path(HWY_INCLUDE_DIR
NAMES hwy/highway.h
HINTS ${PC_HWY_INCLUDEDIR} ${PC_HWY_INCLUDE_DIRS}
)
find_library(HWY_LIBRARY
NAMES ${HWY_NAMES} hwy
HINTS ${PC_HWY_LIBDIR} ${PC_HWY_LIBRARY_DIRS}
)
if (HWY_INCLUDE_DIR AND NOT HWY_VERSION)
if (EXISTS "${HWY_INCLUDE_DIR}/hwy/highway.h")
file(READ "${HWY_INCLUDE_DIR}/hwy/highway.h" HWY_VERSION_CONTENT)
string(REGEX MATCH "#define HWY_MAJOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
set(HWY_VERSION_MAJOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define +HWY_MINOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
set(HWY_VERSION_MINOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define +HWY_PATCH +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
set(HWY_VERSION_PATCH "${CMAKE_MATCH_1}")
set(HWY_VERSION "${HWY_VERSION_MAJOR}.${HWY_VERSION_MINOR}.${HWY_VERSION_PATCH}")
endif ()
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HWY
FOUND_VAR HWY_FOUND
REQUIRED_VARS HWY_LIBRARY HWY_INCLUDE_DIR
VERSION_VAR HWY_VERSION
)
if (HWY_LIBRARY AND NOT TARGET hwy)
add_library(hwy INTERFACE IMPORTED GLOBAL)
if(CMAKE_VERSION VERSION_LESS "3.13.5")
set_property(TARGET hwy PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${HWY_INCLUDE_DIR})
target_link_libraries(hwy INTERFACE ${HWY_LIBRARY})
set_property(TARGET hwy PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_HWY_CFLAGS_OTHER})
else()
target_include_directories(hwy INTERFACE ${HWY_INCLUDE_DIR})
target_link_libraries(hwy INTERFACE ${HWY_LIBRARY})
target_link_options(hwy INTERFACE ${PC_HWY_LDFLAGS_OTHER})
target_compile_options(hwy INTERFACE ${PC_HWY_CFLAGS_OTHER})
endif()
endif()
mark_as_advanced(HWY_INCLUDE_DIR HWY_LIBRARY)
if (HWY_FOUND)
set(HWY_LIBRARIES ${HWY_LIBRARY})
set(HWY_INCLUDE_DIRS ${HWY_INCLUDE_DIR})
endif ()

View File

@ -0,0 +1,59 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(PC_LCMS2 QUIET libLCMS2)
set(LCMS2_VERSION ${PC_LCMS2_VERSION})
endif ()
find_path(LCMS2_INCLUDE_DIR
NAMES lcms2.h
HINTS ${PC_LCMS2_INCLUDEDIR} ${PC_LCMS2_INCLUDE_DIRS}
)
find_library(LCMS2_LIBRARY
NAMES ${LCMS2_NAMES} lcms2 liblcms2 lcms-2 liblcms-2
HINTS ${PC_LCMS2_LIBDIR} ${PC_LCMS2_LIBRARY_DIRS}
)
if (LCMS2_INCLUDE_DIR AND NOT LCMS_VERSION)
file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT)
string(REGEX MATCH "#define[ \t]+LCMS_VERSION[ \t]+([0-9]+)[ \t]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT})
if (LCMS2_VERSION_MATCH)
string(SUBSTRING ${CMAKE_MATCH_1} 0 1 LCMS2_VERSION_MAJOR)
string(SUBSTRING ${CMAKE_MATCH_1} 1 2 LCMS2_VERSION_MINOR)
set(LCMS2_VERSION "${LCMS2_VERSION_MAJOR}.${LCMS2_VERSION_MINOR}")
endif ()
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LCMS2
FOUND_VAR LCMS2_FOUND
REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR
VERSION_VAR LCMS2_VERSION
)
if (LCMS2_LIBRARY AND NOT TARGET lcms2)
add_library(lcms2 INTERFACE IMPORTED GLOBAL)
if(CMAKE_VERSION VERSION_LESS "3.13.5")
set_property(TARGET lcms2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LCMS2_INCLUDE_DIR})
target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY})
set_property(TARGET lcms2 PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_LCMS2_CFLAGS_OTHER})
else()
target_include_directories(lcms2 INTERFACE ${LCMS2_INCLUDE_DIR})
target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY})
target_link_options(lcms2 INTERFACE ${PC_LCMS2_LDFLAGS_OTHER})
target_compile_options(lcms2 INTERFACE ${PC_LCMS2_CFLAGS_OTHER})
endif()
endif()
mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY)
if (LCMS2_FOUND)
set(LCMS2_LIBRARIES ${LCMS2_LIBRARY})
set(LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR})
endif ()

View File

@ -0,0 +1,95 @@
jpeg-xl (0.9.0) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.9.0.
-- JPEG XL Maintainers <jpegxl@google.com> Wed, 11 Jan 2023 16:12:35 +0000
jpeg-xl (0.8) unstable; urgency=medium
* Bump JPEG XL version to 0.8.
-- JPEG XL Maintainers <jpegxl@google.com> Wed, 11 Jan 2023 16:12:34 +0000
jpeg-xl (0.7) unstable; urgency=medium
* Bump JPEG XL version to 0.7.
-- JPEG XL Maintainers <jpegxl@google.com> Mon, 08 Aug 2022 14:43:58 +0000
jpeg-xl (0.6) unstable; urgency=medium
* Bump JPEG XL version to 0.6.
-- JPEG XL Maintainers <jpegxl@google.com> Fri, 10 Sep 2021 16:08:17 +0200
jpeg-xl (0.5.0) unstable; urgency=medium
* Bump JPEG XL version to 0.5.0.
-- JPEG XL Maintainers <jpegxl@google.com> Thu, 12 Aug 2021 23:49:40 +0200
jpeg-xl (0.3.7) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.7.
-- Sami Boukortt <sboukortt@google.com> Mon, 29 Mar 2021 12:14:20 +0200
jpeg-xl (0.3.6) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.6.
-- Sami Boukortt <sboukortt@google.com> Thu, 25 Mar 2021 17:40:58 +0100
jpeg-xl (0.3.5) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.5.
-- Sami Boukortt <sboukortt@google.com> Tue, 23 Mar 2021 15:20:44 +0100
jpeg-xl (0.3.4) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.4.
-- Sami Boukortt <sboukortt@google.com> Tue, 16 Mar 2021 12:13:59 +0100
jpeg-xl (0.3.3) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.3.
-- Sami Boukortt <sboukortt@google.com> Fri, 5 Mar 2021 19:15:26 +0100
jpeg-xl (0.3.2) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.2.
-- Alex Deymo <deymo@google.com> Fri, 12 Feb 2021 21:00:12 +0100
jpeg-xl (0.3.1) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.1.
-- Alex Deymo <deymo@google.com> Tue, 09 Feb 2021 09:48:43 +0100
jpeg-xl (0.3) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.3.
-- Alex Deymo <deymo@google.com> Wed, 27 Jan 2021 22:36:32 +0100
jpeg-xl (0.2) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.2.
-- Alex Deymo <deymo@google.com> Wed, 23 Nov 2020 20:42:10 +0100
jpeg-xl (0.1) UNRELEASED; urgency=medium
* JPEG XL format release candidate.
-- Alex Deymo <deymo@google.com> Fri, 13 Nov 2020 17:42:24 +0100
jpeg-xl (0.0.2-1) UNRELEASED; urgency=medium
* Initial debian package.
-- Alex Deymo <deymo@google.com> Tue, 27 Oct 2020 15:27:59 +0100

View File

@ -0,0 +1 @@
10

View File

@ -0,0 +1,88 @@
Source: jpeg-xl
Maintainer: JPEG XL Maintainers <jpegxl@google.com>
Section: misc
Priority: optional
Standards-Version: 3.9.8
Build-Depends:
asciidoc,
cmake,
debhelper (>= 9),
libbrotli-dev,
libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev,
libgif-dev,
libgimp2.0-dev,
libgmock-dev,
libgoogle-perftools-dev,
libgtest-dev,
libhwy-dev (>= 1.0.0),
libjpeg-dev,
libopenexr-dev,
libpng-dev,
libwebp-dev,
pkg-config,
xdg-utils,
xmlto,
Homepage: https://github.com/libjxl/libjxl
Rules-Requires-Root: no
Package: jxl
Architecture: any
Section: utils
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: JPEG XL Image Coding System - "JXL" (command line utility)
The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and
lossless image compression format. It has a rich feature set and is
particularly optimized for responsive web environments, so that
content renders well on a wide range of devices. Moreover, it includes
several features that help transition from the legacy JPEG format.
.
This package installs the command line utilities.
Package: libjxl-dev
Architecture: any
Section: libdevel
Depends: libjxl (= ${binary:Version}), ${misc:Depends}
libhwy-dev,
Description: JPEG XL Image Coding System - "JXL" (development files)
The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and
lossless image compression format. It has a rich feature set and is
particularly optimized for responsive web environments, so that
content renders well on a wide range of devices. Moreover, it includes
several features that help transition from the legacy JPEG format.
.
This package installs development files.
Package: libjxl
Architecture: any
Multi-Arch: same
Section: libs
Depends: ${shlibs:Depends}, ${misc:Depends}
Pre-Depends: ${misc:Pre-Depends}
Description: JPEG XL Image Coding System - "JXL" (shared libraries)
The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and
lossless image compression format. It has a rich feature set and is
particularly optimized for responsive web environments, so that
content renders well on a wide range of devices. Moreover, it includes
several features that help transition from the legacy JPEG format.
.
This package installs shared libraries.
Package: libjxl-gdk-pixbuf
Architecture: any
Multi-Arch: same
Section: libs
Depends: ${shlibs:Depends}, ${misc:Depends}
Pre-Depends: ${misc:Pre-Depends}
Description: JPEG XL Plugin for gdk-pixbuf
This package installs the required files for reading JPEG XL files in
GTK applications.
Package: libjxl-gimp-plugin
Architecture: any
Multi-Arch: same
Section: graphics
Depends: ${shlibs:Depends}, ${misc:Depends}
Pre-Depends: ${misc:Pre-Depends}
Enhances: gimp
Description: JPEG XL Import and Export Plugin for GIMP
This is a plugin for GIMP version 2.10.x to import and export JPEG XL images.

View File

@ -0,0 +1,199 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: jpeg-xl
Files: *
Copyright: 2020 the JPEG XL Project
License: BSD-3-clause
Files: third_party/libjpeg-turbo/*
Copyright (C)2009-2023 D. R. Commander. All Rights Reserved.
Copyright (C)2015 Viktor Szathmáry. All Rights Reserved.
License: BSD-3-clause
Files: third_party/sjpeg/*
Copyright: 2017 Google, Inc
License: Apache-2.0
Files: third_party/skcms/*
Copyright: 2018 Google Inc.
License: BSD-3-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Files: testdata/external/pngsuite/*
Copyright: Willem van Schaik, 1996, 2011
License: PngSuite License
See http://www.schaik.com/pngsuite/ for details.
.
Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.
Files: testdata/external/raw.pixls/*
Copyright: their respective owners listed in https://raw.pixls.us/
License: CC0-1.0
Files: testdata/external/wesaturate/*
Copyright: their respective owners listed in https://www.wesaturate.com/
License: CC0-1.0
Files: testdata/external/wide-gamut-tests/
Copyright: github.com/codelogic/wide-gamut-tests authors.
License: Apache-2.0
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian systems, the complete text of the Apache License, Version 2
can be found in "/usr/share/common-licenses/Apache-2.0".
License: CC0
Creative Commons Zero v1.0 Universal
.
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT
RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS"
BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS
DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE
INFORMATION OR WORKS PROVIDED HEREUNDER.
.
Statement of Purpose
.
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
.
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use
and efforts of others.
.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with
a Work (the "Affirmer"), to the extent that he or she is an owner of
Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to
the Work and publicly distribute the Work under its terms, with knowledge of
his or her Copyright and Related Rights in the Work and the meaning and
intended legal effect of CC0 on those rights.
.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national implementations
thereof.
.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer
makes the Waiver for the benefit of each member of the public at large and
to the detriment of Affirmer's heirs and successors, fully intending that
such Waiver shall not be subject to revocation, rescission, cancellation,
termination, or any other legal or equitable action to disrupt the quiet
enjoyment of the Work by the public as contemplated by Affirmer's express
Statement of Purpose.
.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the
Waiver is so judged Affirmer hereby grants to each affected person a
royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "License"). The License
shall be deemed effective as of the date CC0 was applied by Affirmer to the
Work. Should any part of the License for any reason be judged legally
invalid or ineffective under applicable law, such partial invalidity or
ineffectiveness shall not invalidate the remainder of the License, and in
such case Affirmer hereby affirms that he or she will not (i) exercise any
of his or her remaining Copyright and Related Rights in the Work or (ii)
assert any associated claims and causes of action with respect to the Work,
in either case contrary to Affirmer's express Statement of Purpose.
.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied, statutory or
otherwise, including without limitation warranties of title,
merchantability, fitness for a particular purpose, non infringement, or the
absence of latent or other defects, accuracy, or the present or absence of
errors, whether or not discoverable, all to the greatest extent permissible
under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
.
For more information, please see:
http://creativecommons.org/publicdomain/zero/1.0/>

View File

@ -0,0 +1,3 @@
usr/bin/*
usr/share/man/man1/cjxl.1
usr/share/man/man1/djxl.1

View File

@ -0,0 +1,4 @@
usr/include/jxl/*.h
usr/lib/*/*.a
usr/lib/*/*.so
usr/lib/*/pkgconfig/*.pc

View File

@ -0,0 +1,3 @@
usr/lib/*/gdk-pixbuf-*/*/loaders/*
usr/share/mime/packages/image-jxl.xml
usr/share/thumbnailers/jxl.thumbnailer

View File

@ -0,0 +1 @@
usr/lib/gimp

View File

@ -0,0 +1 @@
usr/lib/*/libjxl*.so.*

21
third-party/libjxl/libjxl/debian/rules vendored Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh $@ --buildsystem=cmake
override_dh_auto_configure:
# TODO(deymo): Remove the DCMAKE_BUILD_TYPE once builds without NDEBUG
# are as useful as Release builds.
# TODO(szabadka) Re-enable jpegli after tests are fixed on Ubuntu 20.04,
# and debian:buster
dh_auto_configure -- \
-DJPEGXL_VERSION=$(DEB_VERSION) \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DJPEGXL_FORCE_SYSTEM_GTEST=ON \
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_FORCE_SYSTEM_HWY=ON \
-DJPEGXL_ENABLE_JPEGLI=OFF \
-DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \
-DJPEGXL_ENABLE_PLUGINS=ON

View File

@ -0,0 +1 @@
3.0 (quilt)

93
third-party/libjxl/libjxl/deps.sh vendored Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env bash
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# This file downloads the dependencies needed to build JPEG XL into third_party.
# These dependencies are normally pulled by gtest.
set -eu
MYDIR=$(dirname $(realpath "$0"))
# Git revisions we use for the given submodules. Update these whenever you
# update a git submodule.
THIRD_PARTY_BROTLI="36533a866ed1ca4b75cf049f4521e4ec5fe24727"
THIRD_PARTY_HIGHWAY="591ad359a5aa6c320951ebd35f839604c87abe6c"
THIRD_PARTY_SKCMS="b25b07b4b07990811de121c0356155b2ba0f4318"
THIRD_PARTY_SJPEG="e5ab13008bb214deb66d5f3e17ca2f8dbff150bf"
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1"
THIRD_PARTY_LIBJPEG_TURBO="8ecba3647edb6dd940463fedf38ca33a8e2a73d1" # 2.1.5.1
# Download the target revision from GitHub.
download_github() {
local path="$1"
local project="$2"
local varname=`echo "$path" | tr '[:lower:]' '[:upper:]'`
varname="${varname/[\/-]/_}"
local sha
eval "sha=\${${varname}}"
local down_dir="${MYDIR}/downloads"
local local_fn="${down_dir}/${sha}.tar.gz"
if [[ -e "${local_fn}" && -d "${MYDIR}/${path}" ]]; then
echo "${path} already up to date." >&2
return 0
fi
local url
local strip_components=0
if [[ "${project:0:4}" == "http" ]]; then
# "project" is a googlesource.com base url.
url="${project}${sha}.tar.gz"
else
# GitHub files have a top-level directory
strip_components=1
url="https://github.com/${project}/tarball/${sha}"
fi
echo "Downloading ${path} version ${sha}..." >&2
mkdir -p "${down_dir}"
curl -L --show-error -o "${local_fn}.tmp" "${url}"
mkdir -p "${MYDIR}/${path}"
tar -zxf "${local_fn}.tmp" -C "${MYDIR}/${path}" \
--strip-components="${strip_components}"
mv "${local_fn}.tmp" "${local_fn}"
}
is_git_repository() {
local dir="$1"
local toplevel=$(git rev-parse --show-toplevel)
[[ "${dir}" == "${toplevel}" ]]
}
main() {
if is_git_repository "${MYDIR}"; then
cat >&2 <<EOF
Current directory is a git repository, downloading dependencies via git:
git submodule update --init --recursive
EOF
git -C "${MYDIR}" submodule update --init --recursive --depth 1 --recommend-shallow
return 0
fi
# Sources downloaded from a tarball.
download_github third_party/brotli google/brotli
download_github third_party/highway google/highway
download_github third_party/sjpeg webmproject/sjpeg
download_github third_party/skcms \
"https://skia.googlesource.com/skcms/+archive/"
download_github third_party/zlib madler/zlib
download_github third_party/libpng glennrp/libpng
download_github third_party/libjpeg-turbo libjpeg-turbo/libjpeg-turbo
echo "Done."
}
main "$@"

29
third-party/libjxl/libjxl/doc/api.txt vendored Normal file
View File

@ -0,0 +1,29 @@
/* This document is meant for Doxygen use only. If you are looking for the API
* documentation generate it with `./ci.sh release` and look under the
* build/html directory.
*
* This file documents all the groups and defines the order in which they appear
* in Doxygen. Define the @defgroup commands here and use @addtogroup anywhere
* else.
*/
/**
@defgroup libjxl JPEG XL library (libjxl)
@brief The main JPEG XL decoder / encoder library.
@addtogroup libjxl
@{
@defgroup libjxl_decoder JPEG XL Decoder
@defgroup libjxl_encoder JPEG XL Encoder
@defgroup libjxl_common JPEG XL common definitions
@defgroup libjxl_butteraugli Butteraugli metric
@}
@defgroup libjxl_threads JPEG XL Multi-thread library (libjxl_threads)
@brief Additional multi-threaded implementations for the parallel runner.
*/

View File

@ -0,0 +1,82 @@
# Benchmarking
For speed benchmarks on single images in single or multi-threaded decoding
`djxl` can print decoding speed information. See `djxl --help` for details
on the decoding options and note that the output image is optional for
benchmarking purposes.
For a more comprehensive comparison of compression density between multiple
options, the tool `benchmark_xl` can be used (see below).
## Benchmarking with benchmark_xl
We recommend `build/tools/benchmark_xl` as a convenient method for reading
images or image sequences, encoding them using various codecs (jpeg jxl png
webp), decoding the result, and computing objective quality metrics. An example
invocation is:
```bash
build/tools/benchmark_xl --input "/path/*.png" --codec jxl:wombat:d1,jxl:cheetah:d2
```
Multiple comma-separated codecs are allowed. The characters after : are
parameters for the codec, separated by colons, in this case specifying maximum
target psychovisual distances of 1 and 2 (higher implies lower quality) and
the encoder effort (see below). Other common parameters are `r0.5` (target
bitrate 0.5 bits per pixel) and `q92` (quality 92, on a scale of 0-100, where
higher is better). The `jxl` codec supports the following additional parameters:
Speed: `lightning`, `thunder`, `falcon`, `cheetah`, `hare`, `wombat`, `squirrel`,
`kitten`, `tortoise` control the encoder effort in ascending order. This also
affects memory usage: using lower effort will typically reduce memory consumption
during encoding.
* `lightning` and `thunder` are fast modes useful for lossless mode (modular).
* `falcon` disables all of the following tools.
* `cheetah` enables coefficient reordering, context clustering, and heuristics
for selecting DCT sizes and quantization steps.
* `hare` enables Gaborish filtering, chroma from luma, and an initial estimate
of quantization steps.
* `wombat` enables error diffusion quantization and full DCT size selection
heuristics.
* `squirrel` (default) enables dots, patches, and spline detection, and full
context clustering.
* `kitten` optimizes the adaptive quantization for a psychovisual metric.
* `tortoise` enables a more thorough adaptive quantization search.
Mode: JPEG XL has two modes. The default is Var-DCT mode, which is suitable for
lossy compression. The other mode is Modular mode, which is suitable for lossless
compression. Modular mode can also do lossy compression (e.g. `jxl:m:q50`).
* `m` activates modular mode.
Other arguments to benchmark_xl include:
* `--save_compressed`: save codestreams to `output_dir`.
* `--save_decompressed`: save decompressed outputs to `output_dir`.
* `--output_extension`: selects the format used to output decoded images.
* `--num_threads`: number of codec instances that will independently
encode/decode images, or 0.
* `--inner_threads`: how many threads each instance should use for parallel
encoding/decoding, or 0.
* `--encode_reps`/`--decode_reps`: how many times to repeat encoding/decoding
each image, for more consistent measurements (we recommend 10).
The benchmark output begins with a header:
```
Compr Input Compr Compr Compr Decomp Butteraugli
Method Pixels Size BPP # MP/s MP/s Distance Error p norm BPP*pnorm Errors
```
`ComprMethod` lists each each comma-separated codec. `InputPixels` is the number
of pixels in the input image. `ComprSize` is the codestream size in bytes and
`ComprBPP` the bitrate. `Compr MP/s` and `Decomp MP/s` are the
compress/decompress throughput, in units of Megapixels/second.
`Butteraugli Distance` indicates the maximum psychovisual error in the decoded
image (larger is worse). `Error p norm` is a similar summary of the psychovisual
error, but closer to an average, giving less weight to small low-quality
regions. `BPP*pnorm` is the product of `ComprBPP` and `Error p norm`, which is a
figure of merit for the codec (lower is better). `Errors` is nonzero if errors
occurred while loading or encoding/decoding the image.

View File

@ -0,0 +1,166 @@
# Building and Testing
This file describes the building and testing facilities provided by the `ci.sh`
script. It assumes you already have the build environment set up.
## Basic building
To build the JPEG XL software and run its unit tests, run:
```bash
./ci.sh release
```
## Testing
`./ci.sh` build commands including `release`, `opt`, etc. will also run tests.
You can set the environment variable `SKIP_TEST=1` to skip this.
It is possible to manually run all the tests in parallel in all your CPUs with
the command:
```bash
./ci.sh test
```
It is also possible for faster iteration to run a specific test binary directly.
Tests are run with the `ctest` command and arguments passed to `ci.sh test` are
forwarded to `ctest` with the appropriate environment variables set. For
example, to list all the available tests you can run:
```bash
./ci.sh test -N
```
To run a specific test from the list or actually a set of tests matching a
regular expression you can use `ctest`'s parameter `-R`:
```bash
./ci.sh test -R ^MyPrefixTe
```
That command would run any test whose name that starts with `MyPrefixTe`. For
more options run `ctest --help`, for example, you can pass `-j1` if you want
to run only one test at a time instead of our default of multiple tests in
parallel.
## Other commands
Running `./ci.sh` with no parameters shows a list of available commands. For
example, you can run `opt` for optimized developer builds with symbols or
`debug` for debug builds which do not have NDEBUG defined and therefore include
more runtime debug information.
### Cross-compiling
To compile the code for an architecture different than the one you are running
you can pass a
[toolchain file](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html)
to cmake if you have one for your target, or you can use the `BUILD_TARGET`
environment variable in `./ci.sh`. For some targets such the Windows targets
`ci.sh` sets up extra environment variables that are needed for testing.
This assumes that you already have a cross-compiling environment set up and the
library dependencies are already installed for the target architecture as well.
For example, to compile for the `aarch64-linux-gnu` target triplet you can run:
```bash
BUILD_TARGET=aarch64-linux-gnu ./ci.sh release
```
Whenever using a `BUILD_TARGET` or even a custom `BUILD_DIR` these variables
must be set for **every call** to `ci.sh` even calls to `ci.sh test`, for which
we recommend exporting them in your shell session, for example:
```bash
export BUILD_TARGET=x86_64-w64-mingw32 BUILD_DIR=build-foobar
```
### Format checks (lint)
```bash
./ci.sh lint
```
Linter checks will verify that the format of your patch conforms to the project
style. For this, we run clang-format only on the lines that were changed by
your commits.
If your local git branch is tracking `origin/master` and you landed a few
commits in your branch, running this lint command will check all the changes
made from the common ancestor with `origin/master` to the latest changes,
including uncommitted changes. The output of the program will show the patch
that should be applied to fix your commits. You can apply these changes with the
following command from the base directory of the git checkout:
```bash
./ci.sh lint | patch -p1
```
### Programming errors (tidy)
```bash
./ci.sh tidy
```
clang-tidy is a tool to check common programming errors in C++, and other valid
C++ constructions that are discouraged by the style guide or otherwise dangerous
and may constitute a bug.
To run clang-tidy on the files changed by your changes you can run `./ci.sh
tidy`. Note that this will report all the problems encountered in any file that
was modified by one of your commits, not just on the lines that your commits
modified.
### Address Sanitizer (asan)
```bash
./ci.sh asan
```
ASan builds allow to check for invalid address usages, such as use-after-free.
To perform these checks, as well as other undefined behavior checks we only need
to build and run the unittests with ASan enabled which can be easily achieved
with the command above. If you want to have the ASan build files separated from
your regular `build/` directory to quickly switch between asan and regular
builds, you can pass the build directory target as follows:
```bash
BUILD_DIR=build-asan ./ci.sh asan
```
### Memory Sanitizer (msan)
MSan allows to check for invalid memory accesses at runtime, such as using an
uninitialized value which likely means that there is a bug. To run these checks,
a specially compiled version of the project and tests is needed.
For building with MSan, you need to build a version of libc++ with
`-fsanitize=memory` so we can link against it from the MSan build. Also, having
an `llvm-symbolizer` installed is very helpful to obtain stack traces that
include the symbols (functions and line numbers). To install `llvm-symbolizer`
on a Debian-based system run:
```bash
sudo apt install llvm # or llvm-7, etc for a specific version.
```
To install a version of libc++ compiled with `-fsanitize=memory` you can use the
`./ci.sh msan_install` command helper. This will download, compile and install
libc++ and libc++abi in the `${HOME}/.msan` directory to be used later.
After this is set up, you can build the project using the following command:
```bash
./ci.sh msan
```
This command by default uses the `build` directory to store the cmake and object
files. If you want to have a separate build directory configured with msan you
can for example call:
```bash
BUILD_DIR=build-msan ./ci.sh msan
```

View File

@ -0,0 +1,62 @@
# Building WASM artifacts
This file describes the building and testing of JPEG XL
[Web Assembly](https://webassembly.org/) bundles and wrappers.
These instructions assume an up-to-date Debian/Ubuntu system.
For the sake of simplicity, it is considered, that the following environment
variables are set:
* `OPT` - path to the directory containing additional software;
the `emsdk` directory with the Emscripten SDK should reside there.
## Requirements
[CMake](https://cmake.org/) is used as a build system. To install it, follow
[Debian build instructions](developing_in_debian.md).
[Emscripten SDK](https://emscripten.org/) is required for building
WebAssembly artifacts. To install it, follow the
[Download and Install](https://emscripten.org/docs/getting_started/downloads.html)
guide:
```bash
cd $OPT
# Get the emsdk repo.
git clone https://github.com/emscripten-core/emsdk.git
# Enter that directory.
cd emsdk
# Download and install the latest SDK tools.
./emsdk install latest
# Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file)
./emsdk activate latest
```
## Building and testing the project
```bash
# Setup EMSDK and other environment variables. In practice EMSDK is set to be
# $OPT/emsdk.
source $OPT/emsdk/emsdk_env.sh
# This should set the $EMSDK variable.
# If your node version is <16.4.0, you might need to update to a newer version or override
# the node binary with a version which supports SIMD:
echo "NODE_JS='/path/to/node_binary'" >> $EMSDK/.emscripten
# Assuming you are in the root level of the cloned libjxl repo,
# either build with regular WASM:
BUILD_TARGET=wasm32 emconfigure ./ci.sh release
# or with SIMD WASM:
BUILD_TARGET=wasm32 ENABLE_WASM_SIMD=1 emconfigure ./ci.sh release
```
## Example site
Once you have build the wasm binary, you can give it a try by building a site
that decodes jxl images, see [wasm_demo](../tools/wasm_demo/README.md).

View File

@ -0,0 +1,68 @@
# Color Management
[TOC]
<!--*
# Document freshness: For more information, see go/fresh-source.
freshness: { owner: 'sboukortt' reviewed: '2022-09-27' }
*-->
## Why
The vast majority of web images are still sRGB. However, wide-gamut material is
increasingly being produced (photography, cinema, 4K). Screens covering most of
the Adobe RGB gamut are readily available and some also cover most of DCI P3
(iPhone, Pixel2) or even BT.2020.
Currently, after a camera records a very saturated red pixel, most raw
processors would clip it to the rather small sRGB gamut before saving as JPEG.
In keeping with our high-quality goal, we prevent such loss by allowing wider
input color spaces.
## Which color space
Even wide gamuts could be expressed relative to the sRGB primaries, but the
resulting coordinates may be outside the valid 0..1 range. Surprisingly, such
'unbounded' coordinates can be passed through color transforms provided the
transfer functions are expressed as parametric functions (not lookup tables).
However, most image file formats (including PNG and PNM) lack min/max metadata
and thus do not support unbounded coordinates.
Instead, we need a larger working gamut to ensure most pixel coordinates are
within bounds and thus not clipped. However, larger gamuts result in lower
precision/resolution when using <= 16 bit encodings (as opposed to 32-bit float
in PFM). BT.2100 or P3 DCI appear to be good compromises.
## CMS library
Transforms with unbounded pixels are desirable because they reduce round-trip
error in tests. This requires parametric curves, which are only supported for
the common sRGB case in ICC v4 profiles. ArgyllCMS does not support v4. The
other popular open-source CMS is LittleCMS. It is also used by color-managed
editors (Krita/darktable), which increases the chances of interoperability.
However, LCMS has race conditions and overflow issues that prevent fuzzing. We
will later switch to the newer skcms. Note that this library does not intend to
support multiProcessElements, so HDR transfer functions cannot be represented
accurately. Thus in the long term, we will probably migrate away from ICC
profiles entirely.
## Which viewer
On Linux, Krita and darktable support loading our PNG output images and their
ICC profile.
## How to compress/decompress
### Embedded ICC profile
- Create an 8-bit or 16-bit PNG with an iCCP chunk, e.g. using darktable.
- Pass it to `cjxl`, then `djxl` with no special arguments. The decoded output
will have the same bit depth (can override with `--output_bit_depth`) and
color space.
### Images without metadata (e.g. HDR)
- Create a PGM/PPM/PFM file in a known color space.
- Invoke `cjxl` with `-x color_space=RGB_D65_202_Rel_Lin` (linear 2020). For
details/possible values, see color_encoding.cc `Description`.
- Invoke `djxl` as above with no special arguments.

View File

@ -0,0 +1,56 @@
# Developing in Debian
These instructions assume an up-to-date Debian/Ubuntu system.
For other platforms, please instead use the following:
* [Cross Compiling for Windows with Crossroad](developing_with_crossroad.md).
## Minimum build dependencies
Apart from the dependencies in `third_party`, some of the tools use external
dependencies that need to be installed on your system first:
```bash
sudo apt install cmake clang doxygen g++ extra-cmake-modules \
libgif-dev libjpeg-dev ninja-build libgoogle-perftools-dev
```
Make sure your default `clang` compiler is at least version 6 by running
```bash
clang --version
```
If it still shows an old version despite having, for example, `clang-7` installed, you need
to update the default `clang` compiler. On Debian-based systems run:
```bash
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100
```
Optionally, to compile some of the extra tool support and tests you can install
the following packages:
```bash
sudo apt install qt6-base-dev libwebp-dev libgimp2.0-dev libopenexr-dev \
libgtest-dev libgmock-dev libbenchmark-dev libbenchmark-tools
```
For the lint/coverage commands, you will also need additional packages:
```bash
sudo apt install clang-format clang-tidy curl parallel gcovr
```
## Building
The `libjxl` project uses CMake to build. We provide a script that simplifies the
invocation. To build and test the project, run
```bash
./ci.sh opt
```
This writes binaries to `build/tools` and runs unit tests. More information
on [build modes and testing](building_and_testing.md) is available.

View File

@ -0,0 +1,357 @@
# Developing in GitHub
This document describes the development steps related to handling the git
repository.
If you are new to GitHub, there's a nice [quickstart
guide](https://docs.github.com/en/github/getting-started-with-github/quickstart)
on GitHub explaining the basics.
## Initial setup
You need to perform this set up at least once if you haven't use GitHub before.
Read through the quickstart guide [Set up
Git](https://docs.github.com/en/github/getting-started-with-github/set-up-git)
page to get your git up and running. You will need to Fork a repository next.
After that "Life of a Pull Request" describes the common everyday workflows.
### Configure your SSH access
The easiest way to configure access to your Github repository is to use SSH
keys. For that you need an SSH private and public key, ideally a strong one. You
can use different keys for different sites if you want. In this example, we will
create one for using in GitHub only.
Create the `~/.ssh/id_rsa_github` file executing the following. (Here and
elsewhere, {{X}} are placeholders for your email/username)
```bash
ssh-keygen -t rsa -b 4096 -C "{{EMAIL}}" -f ~/.ssh/id_rsa_github
```
Go to your [SSH and GPG keys](https://github.com/settings/keys) settings and
paste the contents of your *public key* (the one ending in `.pub`), that would
be the output of this command:
```bash
cat ~/.ssh/id_rsa_github.pub
```
To use a specific key when SSHing to the github.com domain, you can add this
snippet of config to your .ssh/config file executing the following.
```bash
cat >> ~/.ssh/config <<EOF
Host github.com
Hostname github.com
IdentityFile ~/.ssh/id_rsa_github
IdentitiesOnly yes
EOF
```
The `IdentitiesOnly yes` part forces to only use the provided IdentityFile when
talking to GitHub.
### Fork your private copy
The JPEG XL code is located in [this repo](https://github.com/libjxl/libjxl).
The normal developer workflow in GitHub involves creating your own fork of a
repository and uploading your own changes there. From your own copy you can
request merges *to* the upstream repository directly, there's no need to create
a branch in the upstream repository.
[Fork the
repository](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo)
in GitHub to create your own copy of the repository in GitHub. You can then
propose to include changes in the main repository via a Pull Request.
Once you are done you should have your repository at
https://<!-- not a link -->github.com<!-- not a link -->/*{{USERNAME}}*/libjxl
where {{USERNAME}} denotes your GitHub username.
### Checkout the JPEG XL code from GitHub
To get the source code on your computer you need to "clone" it. There are two
repositories at play here, the upstream repository (`libjxl/lbjxl`) and your
fork (`{{USERNAME}}/libjxl`). You will be normally fetching new changes from
the upstream repository and push changes to your fork. Getting your changes from
your fork to the upstream repository is done through the Web interface, via Pull
Requests.
The [Fork a
repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo)
goes in great detail, but uses the git remote names `upstream` for the shared
upstream repository and `origin` for your work. This guide proposes an
alternative naming scheme, used in the examples below.
In this guide `origin` is the upstream shared repository and `myfork` is your
fork. You can use any other name for your fork if you want. Use the following
commands to set things up, replacing `{{USERNAME}}` with your GitHub username:
```bash
git clone git https://github.com/libjxl/libjxl --recursive
cd libjxl
git remote set-url --push origin git@github.com:{{USERNAME}}/libjxl.git
git remote add myfork git@github.com:{{USERNAME}}/libjxl.git
git remote -vv
```
These commands did three things:
* Created the repository with `origin` as the upstream remote,
* Changed the "push" URL to point to your fork, and
* Create a new remote pointing to your fork.
The last step is optional. Since the "fetch" URL of `origin` points to the
shared repository and the "push" URL points to your fork, fetching from `origin`
always gets the latest changes from the upstream repository regardless of the
contents of your fork.
Having a second origin called `myfork` is only useful if you need to download
pending changes from your fork from a different computer. For example, if you
work on multiple computers, each one with this setup, you can push to your
fork from one, and then fetch from `myfork` from another computer to get those.
# Life of a Pull Request
The general [GitHub flow
guide](https://docs.github.com/en/github/getting-started-with-github/github-flow)
applies to sending Pull Requests to this project.
All the commands here assume you are in a git checkout as setup here.
### Sync to the latest version
```bash
git fetch origin
```
The last upstream version is now on `origin/main` and none of your local
branches have been modified by this command.
### Start a new branch
To start a new change you need a local branch. Each branch will represent a list
of individual commits which can then be requested to be merged as a single merge
request. So in general one branch is one code review, but each branch can have
multiple individual commits in it.
```bash
git checkout origin/main -b mybranch
```
This will create a new branch `mybranch` tracking `origin/main`. A branch can
track any remove or local branch, which is used by some tools. Running `git
branch -vv` will show all the branches you have have, what are they tracking and
how many commits are ahead or behind. If you create a branch without tracking
any other, you can add or change the tracking branch of the current branch
running `git branch --set-upstream-to=...`.
### Add changes to your branch
Follow any of the many online tutorials, for example
[The basics](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository)
chapter from the https://git-scm.com/doc website is a good starting guide.
Create, change or delete files and do a git commit with a message.
The commit message is required. A commit message should follow the 50/72 rule:
* First line is 50 characters or less.
* Then a blank line.
* Remaining text should be wrapped at 72 characters.
The first line should identify your commit, since that's what most tools will
show to the user. First lines like "Some fixes" are not useful. Explain what the
commit contains and why.
We follow the [Google C++ Coding
Style](https://google.github.io/styleguide/cppguide.html). A
[clang-format](https://clang.llvm.org/docs/ClangFormat.html) configuration
file is available to automatically format your code, you can invoke it with
the `./ci.sh lint` helper tool.
Read the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more information about
contributing to libjxl.
### Upload your changes for review
The first step is a local review of your changes to see what will you be sending
for review. `gitg` is a nice Gtk UI for reviewing your local changes, or `tig`
for similar ncurses console-based interface. Otherwise, from the terminal you
can run:
```bash
git branch -vv
```
To show the current status of your local branches. In particular, since your
branch is tracking origin/main (as seen in the output) git will tell you that
you are one commit ahead of the tracking branch.
```
* mybranch e74ae1a [origin/main: ahead 1] Improved decoding speed by 40%
```
It is a good idea before uploading to sync again with upstream (`git fetch
origin`) and then run `git branch -vv` to check whether there are new changes
upstream. If that is the case, you will see a "behind" flag in the output:
```
* mybranch e74ae1a [origin/main: ahead 1, behind 2] Improved decoding speed by 40%
```
To sync your changes on top of the latest changes in upstream you need to
rebase:
```bash
git rebase
```
This will by default rebase your current branch changes on top of the tracking
branch. In this case, this will try to apply the current commit on top of the
latest origin/main (which has 2 more commits than the ones we have in our
branch) and your branch will now include that. There could be conflicts that you
have to deal with. A shortcut to do both fetch and rebase is to run `git pull
-r`, where the `-r` stands for "rebase" and will rebase the local commits on top
of the remote ones.
Before uploading a patch, make sure your patch conforms to the
[contributing guidelines](../CONTRIBUTING.md) and it
[builds and passes tests](building_and_testing.md).
Once you are ready to send your branch for review, upload it to *your* fork:
```bash
git push origin mybranch
```
This will push your local branch "mybranch" to a remote in your fork called
"mybranch". The name can be anything, but keep in mind that it is public. A link
to the URL to create a merge request will be displayed.
```
Enumerating objects: 627, done.
Counting objects: 100% (627/627), done.
Delta compression using up to 56 threads
Compressing objects: 100% (388/388), done.
Writing objects: 100% (389/389), 10.71 MiB | 8.34 MiB/s, done.
Total 389 (delta 236), reused 0 (delta 0)
emote:
remote: Create a pull request for 'mybranch' on GitHub by visiting:
remote: https://github.com/{{USERNAME}}/libjxl/pull/new/mybranch
remote:
To github.com:{{USERNAME}}/libjxl.git
* [new branch] mybranch -> mybranch
```
### Updating submodules
The repository uses submodules for external library dependencies in
third_party. Each submodule points to a particular external commit of the
external repository by the hash code of that external commit. Just like
regular source code files, this hash code is part of the current branch and
jpeg xl commit you have checked out.
When changing branches or when doing `git rebase`, git will unfortunately
*not* automatically set those hashes to the ones of the branch or jpeg xl
commit you changed to nor set the source files of the third_party submodules
to the new state. That is, even though git will have updated the jpeg xl
source code files on your disk to the new ones, it will leave the submodule
hashes and the files in third_party in your workspace to the ones they were
before you changed branches. This will show up in a git diff because this
is seen as a change compared to the branch you switched to. The git diff shows
the difference in hash codes (as if you are changing to the old ones), it does
not show changes in files inside the third_party directory.
This mismatch can cause at least two problems:
*) the jpeg xl codebase may not compile due to third_party library version
mismatch if e.g. API changed or a submodule was added/removed.
*) when using `commit -a` your commit, which may be a technical change
unrelated to submodule changes, will unintentionally contain a change to the
submodules hash code, which is undesired unless you actually want to change
the version of third_party libraries.
To resolve this, the submodules must be updated manually with
the following command after those actions (at least when the submodules
changed):
```
git submodule update --init --recursive
```
Here, the init flag ensures new modules get added when encessary and the
recursive flag is required for the submodules depending on other submodules.
If you checkout a different branch, you can spot that submodules changed
when it shows a message similar to this:
```
M third_party/brotli
M third_party/lcms
```
If you do a rebase you may end up in a harder to solve situation, where
`git submodule update --init --recursive` itself fails with errors such as:
```
Unable to checkout '35ef5c554d888bef217d449346067de05e269b30' in submodule path 'third_party/brotli'
```
In that case, you can use the force flag:
```
git submodule update --init --recursive --force
```
### Iterating changes in your merge request
To address reviewer changes you need to amend the local changes in your branch
first. Make the changes you need in your commit locally by running `git commit
--amend file1 file2 file3 ...` or `git commit --amend -a` to amend all the
changes from all the staged files.
Once you have the new version of the "mybranch" branch to re-upload, you need to
force push it to the same branch in your fork. Since you are pushing a different
version of the same commit (as opposed to another commit on top of the existing
ones), you need to force the operation to replace the old version.
```bash
git push origin mybranch --force
```
The merge request should now be updated with the new changes.
### Merging your changes
We use "rebase" as a merge policy, which means that there a no "merge" commits
(commits with more than one parent) but instead only a linear history of
changes.
It is possible that other changes where added to the main branch since the last
time you rebased your changes. These changes could create a conflict with your
Pull Request, if so you need to `git fetch`, `git rebase` and push again your
changes which need to go through the continuous integration workflow again to
verify that all the tests pass again after including the latest changes.
### Trying locally a pending Pull Request
If you want to review in your computer a pending pull request proposed by
another user you can fetch the merge request commit with the following command,
replacing `NNNN` with the pull request number:
```bash
git fetch origin refs/pull/NNNN/head
git checkout FETCH_HEAD
```
The first command will add to your local git repository the remote commit for
the pending pull request and store a temporary reference called `FETCH_HEAD`.
The second command then checks out that reference. From this point you can
review the files in your computer, create a local branch for this FETCH_HEAD or
build on top of it.

View File

@ -0,0 +1,168 @@
# Developing for Windows with MSYS2
[MSYS2](https://www.msys2.org/) ("minimal system 2") is a software distribution and a development platform based on MinGW and Cygwin. It provides a Unix-like environment to build code on Windows. These instructions were written with a 64-bit instance of Windows 10 running on a VM. They may also work on native instances of Windows and other versions of Windows.
## Build Environments
MSYS2 provides multiple development [environments](https://www.msys2.org/docs/environments/). By convention, they are referred to in uppercase. They target slightly different platforms, runtime libraries, and compiler toolchains. For example, to build for 32-bit Windows, use the MINGW32 environment. For interoperability with Visual Studio projects, use the UCRT64 environment.
Since all of the build environments are built on top of the MSYS environment, **all updates and package installation must be done from within the MSYS environment**. After making any package changes, `exit` all MSYS2 terminals and restart the desired build-environment. This reminder is repeated multiple times throughout this guide.
* **MINGW32:** To compile for 32-bit Windows (on 64-bit Windows), use packages from the `mingw32` group. Package names are prefixed with `mingw-w64-i686`. The naming scheme may be different on the 32-bit version of MSYS2.
* **MINGW64:** This is the primary environment to building for 64-bit Windows. It uses the older MSVCRT runtime, which is widely available across Windows systems. Package names are prefixed with `mingw-w64-x86_64`.
* **UCRT64:** The Universal C Runtime (UCRT) is used by recent versions of Microsoft Visual Studio. It ships by default with Windows 10. For older versions of Windows, it must be provided with the application or installed by the user. Package names are prefixed with `mingw-w64-ucrt-x86_64`.
* **CLANG64:** Unfortunately, the `gimp` packages are not available for the CLANG64 environment. However, `libjxl` will otherwise build in this environment if the appropriate packages are installed. Packages are prefixed with `mingw-w64-clang-x86_64`.
## Install and Upgrade MSYS2
Download MSYS2 from the homepage. Install at a location without any spaces on a drive with ample free space. After installing the packages used in this guide, MSYS2 used about 15GB of space.
Toward the end of installation, select the option to run MSYS2 now. A command-line window will open. Run the following command, and answer the prompts to update the repository and close the terminal.
```bash
pacman -Syu
```
Now restart the MSYS environment and run the following command to complete updates:
```bash
pacman -Su
```
## Package Management
Packages are organized in groups, which share the build environment name, but in lower case. Then they have name prefixes that indicate which group they belong to. Consider this package search: `pacman -Ss cmake`
```
mingw32/mingw-w64-i686-cmake
mingw64/mingw-w64-x86_64-cmake
ucrt64/mingw-w64-ucrt-x86_64-cmake
clang64/mingw-w64-clang-x86_64-cmake
msys/cmake
```
We can see the organization `group/prefix-name`. When installing packages, the group name is optional.
```bash
pacman -S mingw-w64-x86_64-cmake
```
For tools that need to be aware of the compiler to function, install the package that corresponds with the specific build-environment you plan to use. For `cmake`, install the `mingw64` version. The generic `msys/cmake` will not function correctly because it will not find the compiler. For other tools, the generic `msys` version is adequate, like `msys/git`.
To remove packages, use:
```bash
pacman -Rsc [package-name]
```
## Worst-Case Scenario...
If packages management is done within a build environment other than MSYS, the environment structure will be disrupted and compilation will likely fail. If this happens, it may be necessary to reinstall MSYS2.
1. Rename the `msys64` folder to `msys64.bak`.
2. Use the installer to reinstall MSYS2 to `msys64`.
3. Copy packages from `msys64.bak/var/cache/pacman/pkg/` to the new installation to save download time and bandwidth.
4. Use `pacman` from within the MSYS environment to install and update packages.
5. After successfully building a project, it is safe to delete `msys64.bak`
## The MING64 Environment
Next set up the MING64 environment. The following commands should be run within the MSYS environment. `pacman -S` is used to install packages. The `--needed` argument prevents packages from being reinstalled.
```bash
pacman -S --needed base-devel mingw-w64-x86_64-toolchain
pacman -S git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \
mingw-w64-x86_64-gtest mingw-w64-x86_64-giflib \
mingw-w64-x86_64-libpng mingw-w64-x86_64-libjpeg-turbo
```
## Build `libjxl`
Download the source from the libjxl [releases](https://github.com/libjxl/libjxl/releases) page. Alternatively, you may obtain the latest development version with `git`. Run `./deps.sh` to ensure additional third-party dependencies are downloaded.
Start the MINGW64 environment, create a build directory within the source directory, and configure with `cmake`.
```bash
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \
-DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_PLUGINS=ON \
-DJPEGXL_ENABLE_MANPAGES=OFF -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_FORCE_SYSTEM_GTEST=ON ..
```
Check the output to see if any dependencies were missed and need to be installed. Adding `-G Ninja` may be helpful, but on my computer, Ninja was selected by default. Remember that package changes must be done from the MSYS environment. Then exit all MSYS2 terminals and restart the build environment.
If all went well, you may now run `cmake` to build `libjxl`:
```bash
cmake --build .
```
Do not be alarmed by the compiler warnings. They are a caused by differences between gcc/g++ and clang. The build should complete successfully. Then `cjxl`, `djxl`, `jxlinfo`, and others can be run from within the build environment. Moving them into the native Windows environment requires resolving `dll` issues that are beyond the scope of this document.
## The `clang` Compiler
To use the `clang` compiler, install the packages that correspond with the environment you wish to use. Remember to make package changes from within the MSYS environment.
```
mingw-w64-i686-clang
mingw-w64-i686-clang-tools-extra
mingw-w64-i686-clang-compiler-rt
mingw-w64-x86_64-clang
mingw-w64-x86_64-clang-tools-extra
mingw-w64-x86_64-clang-compiler-rt
mingw-w64-ucrt64-x86_64-clang
mingw-w64-ucrt64-x86_64-clang-tools-extra
mingw-w64-ucrt64-x86_64-clang-compiler-rt
```
After the `clang` compiler is installed, 'libjxl' can be built with the `./ci.sh` script.
```bash
./ci.sh opt -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \
-DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON -DJPEGXL_FORCE_SYSTEM_GTEST=ON
```
On my computer, `doxygen` packages needed to be installed to proceed with building. Use `pacman -Ss doxygen` to find the packages to install.
## The GIMP Plugin
To build the GIMP plugin, install the relevant `gimp` package. This will also install dependencies. Again, perform package management tasks from only the MSYS environment. Then restart the build environment.
```bash
pacman -S mingw-w64-i686-gimp
pacman -S mingw-w64-x86_64-gimp
pacman -S mingw-w64-ucrt-x86_64-gimp
```
If `clang` is installed, you can use the `./ci.sh` script to build. Otherwise, navigate to the build directory to reconfigure and build with `cmake`.
```bash
cd build
rm -r CM*
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \
-DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \
-DJPEGXL_ENABLE_PLUGINS=ON -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_FORCE_SYSTEM_GTEST=ON ..
```
The plugin is built statically, so there should be no need to install `dll` files. To try out the plugin:
1. [Download](https://www.gimp.org/downloads/) and install the stable version of GIMP (currently 2.10.24).
2. Create a new folder: `C:\Program Files\GIMP 2\lib\gimp\2.0\plug-ins\file-jxl`
3. Copy `build/plugins/gimp/file-jxl.exe` to the new folder.

View File

@ -0,0 +1,90 @@
# Developing on Windows with Visual Studio 2019
These instructions assume an up-to-date Windows 10 (e.g. build 19041.928) with
**Microsoft Visual Studio 2019** (e.g. Version 16.9.0 Preview 4.0) installed. If
unavailable, please use another build environment:
* [MSYS2 on Windows](developing_in_windows_msys.md)
* [Crossroad on Linux](developing_with_crossroad.md) (cross compilation for Windows)
## Minimum build dependencies
Apart from the dependencies in third_party, some of the tools use external
dependencies that need to be installed in your system first.
Please install [vcpkg](https://vcpkg.readthedocs.io/en/latest/examples/installing-and-using-packages/)
(tested with version 2019.07.18), and use it to install the following libraries:
```
vcpkg install gtest:x64-windows
vcpkg install giflib:x64-windows
vcpkg install libjpeg-turbo:x64-windows
vcpkg install libpng:x64-windows
vcpkg install zlib:x64-windows
```
## Building
From Visual Studio, open the CMakeLists.txt in the JPEG XL root directory.
Right-click the CMakeLists.txt entry in the Folder View of the Solution
Explorer. In the context menu, select CMake Settings. Click on the green plus
to add an x64-Clang configuration and the red minus to remove any non-Clang
configuration (the MSVC compiler is currently not supported). Click on the blue
hyperlink marked "CMakeSettings.json" and an editor will open. Insert the
following text after replacing $VCPKG with the directory where you installed
vcpkg above.
```
{
"configurations": [
{
"name": "x64-Clang-Release",
"generator": "Ninja",
"configurationType": "MinSizeRel",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE=$VCPKG/scripts/buildsystems/vcpkg.cmake",
"buildCommandArgs": "-v",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64" ],
"variables": [
{
"name": "VCPKG_TARGET_TRIPLET",
"value": "x64-windows",
"type": "STRING"
},
{
"name": "JPEGXL_ENABLE_TCMALLOC",
"value": "False",
"type": "BOOL"
},
{
"name": "BUILD_GMOCK",
"value": "True",
"type": "BOOL"
},
{
"name": "gtest_force_shared_crt",
"value": "True",
"type": "BOOL"
},
{
"name": "JPEGXL_ENABLE_FUZZERS",
"value": "False",
"type": "BOOL"
},
{
"name": "JPEGXL_ENABLE_VIEWERS",
"value": "False",
"type": "BOOL"
}
]
}
]
}
```
The project is now ready for use. To build, simply press F7 (or choose
Build All from the Build menu). This writes binaries to
`out/build/x64-Clang-Release/tools`. The main [README.md](../README.md) explains
how to use the encoder/decoder and benchmark binaries.

View File

@ -0,0 +1,116 @@
# Cross Compiling for Windows with Crossroad
[Crossroad](https://pypi.org/project/crossroad/) is a tool to set up cross-compilation environments on GNU/Linux distributions. These instructions assume a Debian/Ubuntu system. However, they can likely be adapted to other Linux environments. Since Ubuntu can be run on Windows through WSL, these instruction may be useful for developing directly on Windows.
## Install Crossroad
Crossroad requires tools included with `python3-docutils` and `mingw-w64`. They may be installed using:
```bash
sudo aptitude install python3-docutils mingw-w64
```
The `zstandard` python package is also required, but is not available in the repositories. It may be installed using `pip`.
```bash
pip3 install zstandard
```
After the dependencies are installed, crossroad itself maybe installed with `pip`.
```bash
pip3 install crossroad
```
If there are errors while running crossroad, it may need to be downloaded and installed directly using `setup.py`. Instructions are on the crossroad homepage.
## Update Debian Alternatives
Since `libjxl` uses C++ features that require posix threads, the symlinks used by the Debian alternative system need to be updated:
```bash
sudo update-alternatives --config x86_64-w64-mingw32-g++
```
Select the option that indicates `posix` usage. Repeat for `gcc` and `i686`:
```bash
sudo update-alternatives --config x86_64-w64-mingw32-gcc
sudo update-alternatives --config i686-w64-mingw32-gcc
sudo update-alternatives --config i686-w64-mingw32-g++
```
## Create a New Crossroad Project
Crossroad supports the following platforms:
```
native Native platform (x86_64 GNU/Linux)
android-x86 Generic Android/Bionic on x86
android-mips64 Generic Android/Bionic on MIPS64
android-x86-64 Generic Android/Bionic on x86-64
w64 Windows 64-bit
w32 Windows 32-bit
android-arm64 Generic Android/Bionic on ARM64
android-mips Generic Android/Bionic on MIPS
android-arm Generic Android/Bionic on ARM
```
To begin cross compiling for Windows, a new project needs to be created:
```bash
crossroad w64 [project-name]
```
## Install Dependencies
Since the `gimp` development package is required to build the GIMP plugin and also includes most of the packages required by `libjxl`, install it first.
```bash
crossroad install gimp
```
`gtest` and `brotli` are also required.
```bash
crossroad install gtest brotli
```
If any packages are later found to be missing, you may search for them using:
```bash
crossroad search [...]
```
## Build `libjxl`
Download the source from the libjxl [releases](https://github.com/libjxl/libjxl/releases) page. Alternatively, you may obtain the latest development version with `git`. Run `./deps.sh` to ensure additional third-party dependencies are downloaded. Unfortunately, the script `./ci.sh` does not work with Crossroad, so `cmake` will need to be called directly.
Create a build directory within the source directory. If you haven't already, start your crossroad project and run `cmake`:
```bash
mkdir build
cd build
crossroad w64 libjxl
crossroad cmake -DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \
-DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \
-DJPEGXL_ENABLE_PLUGINS=ON -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_FORCE_SYSTEM_GTEST=ON ..
```
Check the output to see if any dependencies were missed and need to be installed. If all went well, you may now run `cmake` to build `libjxl`:
```bash
cmake --build .
```
## Try out the GIMP Plugin
The plugin is built statically, so there should be no need to install `dll` files. To try out the plugin:
1. [Download](https://www.gimp.org/downloads/) and install the stable version of GIMP (currently 2.10.24).
2. Create a new folder: `C:\Program Files\GIMP 2\lib\gimp\2.0\plug-ins\file-jxl`
3. Copy `build/plugins/gimp/file-jxl.exe` to the new folder.

View File

@ -0,0 +1,32 @@
# Encode effort settings
Various trade-offs between encode speed and compression performance can be selected in libjxl. In `cjxl`, this is done via the `--effort` (`-e`) option.
Higher effort means slower encoding; generally the higher the effort, the more coding tools are used, computationally more expensive heuristics are used,
and more exhaustive search is performed.
Generally efforts range between `1` and `9`, but there is also `e10` you pass the flag `--allow_expert_options` (in combination with "lossless", i.e. `-d 0`). It is considered an expert option because it can be extremely slow.
For lossy compression, higher effort results in better visual quality at a given filesize, and also better
encoder consistency, i.e. less image-dependent variation in the actual visual quality that is achieved. This means that for lossy compression,
higher effort does not necessarily mean smaller filesizes for every image — some images may be somewhat lower quality than desired when using
lower effort heuristics, and to improve consistency, higher effort heuristics may decide to use more bytes for them.
For lossless compression, higher effort should result in smaller filesizes, although this is not guaranteed;
in particular, e2 can be better than e3 for non-photographic images, and e3 can be better than e4 for photographic images.
The following table describes what the various effort settings do:
|Effort | Modular (lossless) | VarDCT (lossy) |
|-------|--------------------|----------------|
| e1 | fast-lossless, fixed YCoCg RCT, fixed ClampedGradient predictor, simple palette detection, no MA tree (one context for everything), Huffman, simple rle-only lz77 | |
| e2 | global channel palette, fixed MA tree (context based on Gradient-error), ANS, otherwise same as e1 | |
| e3 | same as e2 but fixed Weighted predictor and fixed MA tree with context based on WP-error | only 8x8, basically XYB jpeg with ANS |
| e4 | try both ClampedGradient and Weighted predictor, learned MA tree, global palette | simple variable blocks heuristics, adaptive quantization, coefficient reordering |
| e5 | e4 + patches, local palette / local channel palette, different local RCTs | e4 + gabor-like transform, chroma from luma |
| e6 | e5 + more RCTs and MA tree properties | e5 + error diffusion, full variable blocks heuristics |
| e7 | e6 + more RCTs and MA tree properties | e6 + patches (including dots) |
| e8 | e7 + more RCTs, MA tree properties and Weighted predictor parameters | e7 + Butteraugli iterations for adaptive quantization |
| e9 | e8 + more RCTs, MA tree properties and Weighted predictor parameters, try all predictors | e8 + more Butteraugli iterations |
| e10 | e9 + previous-channel MA tree properties, different group dimensions, exhaustively try various e9 options | |
For the entropy coding (context clustering, lz77 search, hybriduint configuration): slower/more exhaustive search as effort goes up.

View File

@ -0,0 +1,284 @@
# JPEG XL Format Overview
This document gives an overview of the JPEG XL file format and codestream,
its features, and the underlying design rationale.
The aim of this document is to provide general insight into the
format capabilities and design, thus helping developers
better understand how to use the `libjxl` API.
## Codestream and File Format
The JPEG XL format is defined in ISO/IEC 18181. This standard consists of
four parts:
* 18181-1: Core codestream
* 18181-2: File format
* 18181-3: Conformance testing
* 18181-4: Reference implementation
### Core codestream
The core codestream contains all the data necessary to decode and display
still image or animation data. This includes basic metadata like image dimensions,
the pixel data itself, colorspace information, orientation, upsampling, etc.
### File format
The JPEG XL file format can take two forms:
* A 'naked' codestream. In this case, only the image/animation data itself is
stored, and no additional metadata can be included. Such a file starts with the
bytes `0xFF0A` (the JPEG marker for "start of JPEG XL codestream").
* An ISOBMFF-based container. This is a box-based container that includes a
JPEG XL codestream box (`jxlc`), and can optionally include other boxes with
additional information, such as Exif metadata. In this case, the file starts with
the bytes `0x0000000C 4A584C20 0D0A870A`.
### Conformance testing
This part of the standard defines precision bounds and test cases for conforming
decoders, to verify that they implement all coding tools correctly and accurately.
### Reference implementation
The `libjxl` software is the reference implementation of JPEG XL.
## Metadata versus Image Data
JPEG XL makes a clear separation between metadata and image data.
Everything that is needed to correctly display an image is
considered to be image data, and is part of the core codestream. This includes
elements that have traditionally been considered 'metadata', such as ICC profiles
and Exif orientation. The goal is to reduce the ambiguity and potential for
incorrect implementations that can be caused by having a 'black box' codestream
that only contains numerical pixel data, requiring applications to figure out how
to correctly interpret the data (i.e. apply color transforms, upsampling,
orientation, blending, cropping, etc.). By including this functionality in the
codestream itself, the decoder can provide output in a normalized way
(e.g. in RGBA, orientation already applied, frames blended and coalesced),
simplifying things and making it less error-prone for applications.
The remaining metadata, e.g. Exif or XMP, can be stored in the container format,
but it does not influence image rendering. In the case of Exif orientation,
this field has to be ignored by applications, since the orientation in the
codestream always takes precedence (and will already have been applied
transparently by the decoder). This means that stripping metadata can be done
without affecting the displayed image.
## Codestream Features
### Color Management
In JPEG XL, images always have a fully defined colorspace, i.e. it is always
unambiguous how to interpret the pixel values. There are two options:
* Pixel data is in a specified (non-XYB) colorspace, and the decoder will produce
a pixel buffer in this colorspace plus an ICC profile that describes that
colorspace. Mathematically lossless encoding can only use this option.
* Pixel data is in the XYB colorspace, which is an absolute colorspace.
In this case, the decoder can produce a pixel buffer directly in a desired
display space like sRGB, Display-P3 or Rec.2100 PQ.
The image header always contains a colorspace; however, its meaning depends on
which of the above two options were used:
* In the first case (non-XYB), the signaled colorspace defines the
interpretation of the pixel data.
* In the second case (XYB), the signaled colorspace is merely a _suggestion_
of a target colorspace to represent the image in, i.e. it is the colorspace
the original image was in, that has a sufficiently wide gamut and a
suitable transfer curve to represent the image data with high fidelity
using a limited bit depth representation.
Colorspaces can be signaled in two ways in JPEG XL:
* CICP-style Enum values: This is a very compact representation that
covers most or all of the common colorspaces. The decoder can convert
XYB to any of these colorspaces without requiring an external color management
library.
* ICC profiles: Arbitrary ICC profiles can also be used, including
CMYK ones. The ICC profile data gets compressed. In this case, external
color management software (e.g. lcms2 or skcms) has to be used for color
conversions.
### Frames
A JPEG XL codestream contains one or more frames. In the case of animation,
these frames have a duration and can be looped (infinitely or a number of times).
Zero-duration frames are possible and represent different layers of the image.
Frames can have a blendmode (Replace, Add, Alpha-blend, Multiply, etc.) and
they can use any previous frame as a base.
They can be smaller than the image canvas, in which case the pixels outside the
crop are copied from the base frame. They can be positioned at an arbitrary
offset from the image canvas; this offset can also be negative and frames can
also be larger than the image canvas, in which case parts of the frame will
be invisible and only the intersection with the image canvas will be shown.
By default, the decoder will blend and coalesce frames, producing only a single
output frame when there are subsequent zero-duration frames, and all output frames
are of the same size (the size of the image canvas) and have either no duration
(in case of a still image) or a non-zero duration (in case of animation).
### Pixel Data
Every frame contains pixel data encoded in one of two modes:
* VarDCT mode: In this mode, variable-sized DCT transforms are applied
and the image data is encoded in the form of DCT coefficients. This mode is
always lossy, but it can also be used to losslessly represent an existing
(already lossy) JPEG image, in which case only the DCT8x8 is used.
* Modular mode: In this mode, only integer arithmetic is used, which
enables lossless compression. However, this mode can also be used for lossy
compression. Multiple transformations can be used to improve compression or to
obtain other desirable effects: reversible color transforms (RCTs),
(delta) palette transforms, and a modified non-linear Haar transform
called Squeeze, which facilitates (but does not require) lossy compression
and enables progressive decoding.
Internally, the VarDCT mode uses Modular sub-bitstreams to encode
various auxiliary images, such as the "LF image" (a 1:8 downscaled version
of the image that contains the DC coefficients of DCT8x8 and low-frequency
coefficients of the larger DCT transforms), extra channels besides the
three color channels (e.g. alpha), and weights for adaptive quantization.
In addition, both modes can separately encode additional 'image features' that
are rendered on top of the decoded image:
* Patches: rectangles from a previously decoded frame (which can be a
'hidden' frame that is not displayed but only stored to be referenced later)
can be blended using one of the blendmodes on top of the current frame.
This allows the encoder to identify repeating patterns (such as letters of
text) and encode them only once, using patches to insert the pattern in
multiple spots. These patterns are encoded in a previous frame, making
it possible to add Modular-encoded pixels to a VarDCT-encoded frame or
vice versa.
* Splines: centripetal Catmull-Rom splines can be encoded, with a color
and a thickness that can vary along the arclength of the curve.
Although the current encoder does not use this bitstream feature yet, we
anticipate that it can be useful to complement DCT-encoded data, since
thin lines are hard to represent faithfully using the DCT.
* Noise: luma-modulated synthetic noise can be added to an image, e.g.
to emulate photon noise, in a way that avoids poor compression due to
high frequency DCT coefficients.
Finally, both modes can also optionally apply two filtering methods to
the decoded image, which both have the goal of reducing block artifacts
and ringing:
* Gabor-like transform ('Gaborish'): a small (3x3) blur that gets
applied across block and group boundaries, reducing blockiness. The
encoder applies the inverse sharpening transform before encoding,
effectively getting the benefits of lapped transforms without the
disadvantages.
* Edge-preserving filter ('EPF'): similar to a bilateral filter,
this smoothing filter avoids blurring edges while reducing ringing.
The strength of this filter is signaled and can locally be adapted.
### Groups
In both modes (Modular and VarDCT), the frame data is signaled as
a sequence of groups. These groups can be decoded independently,
and the frame header contains a table of contents (TOC) with bitstream
offsets for the start of each group. This enables parallel decoding,
and also partial decoding of a region of interest or a progressive preview.
In VarDCT mode, all groups have dimensions 256x256 (or smaller at the
right and bottom borders). First the LF image is encoded, also in
256x256 groups (corresponding to 2048x2048 pixels, since this data
corresponds to the 1:8 image). This means there is always a basic
progressive preview available in VarDCT mode.
Optionally, the LF image can be encoded separately in a (hidden)
LF frame, which can itself recursively be encoded in VarDCT mode
and have its own LF frame. This makes it possible to represent huge
images while still having an overall preview that can be efficiently
decoded.
Then the HF groups are encoded, corresponding to the remaining AC
coefficients. The HF groups can be encoded in multiple passes for
more progressive refinement steps; the coefficients of all passes
are added. Unlike JPEG progressive scan scripts, JPEG XL allows
signaling any amount of detail in any part of the image in any pass.
In Modular mode, groups can have dimensions 128x128, 256x256, 512x512
or 1024x1024. If the Squeeze transform was used, the data will
be split in three parts: the Global groups (the top of the Laplacian
pyramid that fits in a single group), the LF groups (the middle part
of the Laplacian pyramid that corresponds to the data needed to
reconstruct the 1:8 image) and the HF groups (the base of the Laplacian
pyramid), where the HF groups are again possibly encoded in multiple
passes (up to three: one for the 1:4 image, one for the 1:2 image,
and one for the 1:1 image).
In case of a VarDCT image with extra channels (e.g. alpha), the
VarDCT groups and the Modular groups are interleaved in order to
allow progressive previews of all the channels.
The default group order is to encode the LF and HF groups in
scanline order (top to bottom, left to right), but this order
can be permuted arbitrarily. This allows, for example, a center-first
ordering or a saliency-based ordering, causing the bitstream
to prioritize progressive refinements in a different way.
## File Format Features
Besides the image data itself (stored in the `jxlc` codestream box),
the optional container format allows storing additional information.
## Metadata
Three types of metadata can be included in a JPEG XL container:
* Exif (`Exif`)
* XMP (`xml `)
* JUMBF (`jumb`)
This metadata can contain information about the image, such as copyright
notices, GPS coordinates, camera settings, etc.
If it contains rendering-impacting information (such as Exif orientation),
the information in the codestream takes precedence.
## Compressed Metadata
The container allows the above metadata to be stored either uncompressed
(e.g. plaintext XML in the case of XMP) or by Brotli-compression.
In the latter case, the box type is `brob` (Brotli-compressed Box) and
the first four bytes of the box contents define the actual box type
(e.g. `xml `) it represents.
## JPEG Bitstream Reconstruction Data
JPEG XL can losslessly recompress existing JPEG files.
The general design philosophy still applies in this case:
all the image data is stored in the codestream box, including the DCT
coefficients of the original JPEG image and possibly an ICC profile or
Exif orientation.
In order to allow bit-identical reconstruction of the original JPEG file
(not just the image but the actual file), additional information is needed,
since the same image data can be encoded in multiple ways as a JPEG file.
The `jbrd` box (JPEG Bitstream Reconstruction Data) contains this information.
Typically it is relatively small. Using the image data from the codestream,
the JPEG bitstream reconstruction data, and possibly other metadata boxes
that were present in the JPEG file (Exif/XMP/JUMBF), the exact original
JPEG file can be reconstructed.
This box is not needed to display a recompressed JPEG image; it is only
needed to reconstruct the original JPEG file.
## Frame Index
The container can optionally store a `jxli` box, which contains an index
of offsets to keyframes of a JPEG XL animation. It is not needed to display
the animation, but it does facilitate efficient seeking.
## Partial Codestream
The codestream can optionally be split into multiple `jxlp` boxes;
conceptually, this is equivalent to a single `jxlc` box that contains the
concatenation of all partial codestream boxes.
This makes it possible to create a file that starts with
the data needed for a progressive preview of the image, followed by
metadata, followed by the remaining image data.

184
third-party/libjxl/libjxl/doc/fuzzing.md vendored Normal file
View File

@ -0,0 +1,184 @@
# Fuzzing
Fuzzing is a technique to find potential bugs by providing randomly generated
invalid inputs. To detect potential bugs such as programming errors we use
fuzzing in combination with ASan (Address Sanitizer), MSan (Memory Sanitizer),
UBSan (Undefined Behavior Sanitizer) and asserts in the code. An invalid input
will likely produce a decoding error (some API function returning error), which
is absolutely not a problem, but what it should not do is access memory out of
bounds, use uninitialized memory or hit a false assert condition.
## Automated Fuzzing with oss-fuzz
libjxl fuzzing is integrated into [oss-fuzz](https://github.com/google/oss-fuzz)
as the project `libjxl`. oss-fuzz regularly runs the fuzzers on the `main`
branch and reports bugs into their bug tracker which remains private until the
bugs are fixed in main.
## Fuzzer targets
There are several fuzzer executable targets defined in the `tools/` directory
to fuzz different parts of the code. The main one is `djxl_fuzzer`, which uses
the public C decoder API to attempt to decode an image. The fuzzer input is not
directly the .jxl file, the last few bytes of the fuzzer input are used to
decide *how* will the API be used (if preview is requested, the pixel format
requested, if the .jxl input data is provided altogether, etc) and the rest of
the fuzzer input is provided as the .jxl file to the decoder. Some bugs might
reproduce only if the .jxl input is decoded in certain way.
The remaining fuzzer targets execute a specific portion the codec that might be
easier to fuzz independently from the whole codec.
## Reproducing fuzzer bugs
A fuzzer target, like `djxl_fuzzer` accepts as a parameter one or more files
that will be used as inputs. This runs the fuzzer program in test-only mode
where no new inputs are generated and only the provided files are tested. This
is the easiest way to reproduce a bug found by the fuzzer using the generated
test case from the bug report.
oss-fuzz uses a specific compiler version and flags, and it is built using
Docker. Different compiler versions will have different support for detecting
certain actions as errors, so we want to reproduce the build from oss-fuzz as
close as possible. To reproduce the build as generated by oss-fuzz there are a
few helper commands in `ci.sh` as explained below.
### Generate the gcr.io/oss-fuzz/libjxl image
First you need the ossfuzz libjxl builder image. This is the base oss-fuzz
builder image with a few dependencies installed. To generate it you need to
check out the oss-fuzz project and build it:
```bash
git clone https://github.com/google/oss-fuzz.git ~/oss-fuzz
cd ~/oss-fuzz
sudo infra/helper.py build_image libjxl
```
This will create the `gcr.io/oss-fuzz/libjxl` docker image. You can check if it
was created verifying that it is listed in the output of the `sudo docker image
ls` command.
### Build the fuzzer targets with oss-fuzz
To build the fuzzer targets from the current libjxl source checkout, use the
`./ci.sh ossfuzz_msan` command for MSan, `./ci.sh ossfuzz_asan` command for ASan
or `./ci.sh ossfuzz_ubsan` command for UBSan. All the `JXL_ASSERT` and
`JXL_DASSERT` calls are enabled in all the three modes. These ci.sh helpers will
reproduce the oss-fuzz docker call to build libjxl mounting the current source
directory into the Docker container. Ideally you will run this command in a
different build directory separated from your regular builds.
For example, for MSan builds run:
```bash
BUILD_DIR=build-fuzzmsan ./ci.sh ossfuzz_msan
```
After this, the fuzzer program will be generated in the build directory like
for other build modes: `build-fuzzmsan/tools/djxl_fuzzer`.
### Iterating changes with oss-fuzz builds
After modifying the source code to fix the fuzzer-found bug, or to include more
debug information, you can rebuild only a specific fuzzer target to save on
rebuilding time and immediately run the test case again. For example, for
rebuilding and testing only `djxl_fuzzer` in MSan mode we can run:
```bash
BUILD_DIR=build-fuzzmsan ./ci.sh ossfuzz_msan djxl_fuzzer && build-fuzzmsan/tools/djxl_fuzzer path/to/testcase.bin
```
When MSan and ASan fuzzers fail they will print a stack trace at the point where
the error occurred, and some related information. To make these these stack
traces useful we need to convert the addresses to function names and source file
names and lines, which is done with the "symbolizer". For UBSan to print a stack
trace we need to set the `UBSAN_OPTIONS` environment variables when running the
fuzzer.
Set the following environment variables when testing the fuzzer binaries. Here
`clang` should match the compiler version used by the container, you can pass a
different compiler version in the following example by first installing the
clang package for that version outside the container and using `clang-NN`
(for example `clang-11`) instead of `clang` in the following commands:
```bash
symbolizer=$($(realpath $(which clang)) -print-prog-name=llvm-symbolizer)
export MSAN_SYMBOLIZER_PATH="${symbolizer}"
export UBSAN_SYMBOLIZER_PATH="${symbolizer}"
export ASAN_SYMBOLIZER_PATH="${symbolizer}"
export ASAN_OPTIONS=detect_leaks=1
export UBSAN_OPTIONS=print_stacktrace=1
```
Note: The symbolizer binary must be a program called `llvm-symbolizer`, any
other file name will fail. There are normally symlinks already installed with
the right name which the `-print-prog-name` would print.
## Running the fuzzers locally
Running the fuzzer targets in fuzzing mode can be achieved by running them with
no parameters, or better with a parameter with the path to a *directory*
containing a seed of files to use as a starting point. Note that passing a
directory is considered a corpus to use for fuzzing while passing a file is
considered an input to evaluate. Multi-process fuzzing is also supported. For
details about all the fuzzing options run:
```bash
build-fuzzmsan/tools/djxl_fuzzer -help=1
```
## Writing fuzzer-friendly code
Fuzzing on itself can't find programming bugs unless an input makes the program
perform an invalid operation (read/write out of bounds, perform an undefined
behavior operation, etc). You can help the fuzzer find invalid situations by
adding asserts:
* `JXL_ASSERT()` is enabled in Release mode by default. It can be disabled
with `-DJXL_ENABLE_ASSERT=0` but the intention is that it will run for all
the users in released code. If performance of the check is not an issue (like
checks done once per image, once per channel, once per group, etc) a
JXL_ASSERT is appropriate. A failed assert is preferable to an out of bounds
write.
* `JXL_DASSERT()` is only enabled in Debug builds, which includes all the ASan,
MSan and UBSan builds. Performance of these checks is not an issue if kept
within reasonable limits (automated msan/asan test should finish withing 1
hour for example). Fuzzing is more effective when the given input runs
faster, so keep that in mind when adding a complex DASSERT that runs multiple
times per output pixel.
* For MSan builds it is also possible to specify that certain values must be
initialized. This is automatic for values that are used to make decisions
(like when used in an `if` statement or in the ternary operator condition)
but those checks can be made explicit for image data using the
`JXL_CHECK_IMAGE_INITIALIZED(image, rect)` macro. This helps document and
check (only in MSan builds) that a given portion of the image is expected to
be initialized, allowing to catch errors earlier in the process.
## Dealing with use-of-uninitialized memory
In MSan builds it is considered an error to *use* uninitialized memory. Using
the memory normally requires something like a decision / branch based on the
uninitialized value, just running `memcpy()` or simple arithmetic over
uninitialized memory is not a problem. Notably, computing `DemoteTo()`,
`NearestInt()` or similar expressions that create a branch based on the value of
the uninitialized memory will trigger an MSan error.
In libjxl we often run vectorized operations over a series of values, rounding
up to the next multiple of a vector size, thus operating over uninitialized
values past the end of the requested region. These values are part of the image
padding but are not initialized. This behavior would not create an MSan error
unless the processing includes operations like `NearestInt()`. For such cases
the preferred solution is to use `msan::UnpoisonMemory` over the portion of
memory of the last SIMD vector before processing, and then running
`msan::PoisonMemory` over the corresponding value in the output side. A note
including why this is safe to do must be added, for example if the processing
doesn't involve any cross-lane computation.
Initializing padding memory in MSan builds is discouraged because it may hide
bugs in functions that weren't supposed to read from the padding. Initializing
padding memory in all builds, including Release builds, would mitigate the
MSan potential security issue but it would hide the logic bug for a longer time
and potentially incur in a performance hit.

1
third-party/libjxl/libjxl/doc/jxl.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="891.6" height="836.9" viewBox="0 0 891.6 836.9" overflow="visible"><style>.st0{fill:#5fb4b1}</style><path class="st0" d="M495.7 420.6C533 348.3 570.2 276 607.4 203.7H503.8c-24 46.6-48.1 93.3-72.1 139.9-38.8-46.6-77.6-93.3-116.4-139.9H211.7L392 420.6c-36.5 70.8-73 141.7-109.5 212.5h103.6c23.3-45.2 46.6-90.4 69.8-135.5 37.6 45.2 75.1 90.4 112.7 135.5h103.6c-58.7-70.8-117.6-141.6-176.5-212.5zM153 625.6l.3 2.3.7 2.6c3.8 15.1 8.9 59.5-12 86.3-6.2 8-14.8 14.5-25.6 19.3L53.9 836.9c36.9 0 69.4-5.8 96.5-17.4 25.9-11 47.2-27.2 63.2-48.1 22.2-28.9 33.9-66.6 33.8-109.1 0-24.8-4-44.6-5.7-52L200.8 337h.1v-90.2H0V337h109.8L153 625.6zM738.5 211.2l-.3-2.3-.7-2.6c-3.8-15.1-8.9-59.5 12-86.3 6.2-8 14.8-14.5 25.6-19.3L837.6 0c-36.9 0-69.4 5.8-96.5 17.4-25.9 11-47.2 27.2-63.2 48.1-22.2 28.9-33.9 66.6-33.8 109.1 0 24.8 4 44.6 5.7 52l40.9 273.3h-.1v90.2h200.9v-90.2H781.7l-43.2-288.7z"/><path class="st0" d="M153 625.6l.3 2.3.7 2.6c3.8 15.1 8.9 59.5-12 86.3-6.2 8-14.8 14.5-25.6 19.3L53.9 836.9c36.9 0 69.4-5.8 96.5-17.4 25.9-11 47.2-27.2 63.2-48.1 22.2-28.9 33.9-66.6 33.8-109.1 0-24.8-4-44.6-5.7-52L200.8 337h.1v-90.2H0V337h109.8L153 625.6z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,102 @@
cjxl(1)
=======
:doctype: manpage
Name
----
cjxl - compress images to JPEG XL
Synopsis
--------
*cjxl* ['options'...] 'input' ['output.jxl']
Description
-----------
`cjxl` compresses an image or animation to the JPEG XL format. It is intended to
spare users the trouble of determining a set of optimal parameters for each
individual image. Instead, for a given target quality, it should provide
consistent visual results across various kinds of images. The defaults have been
chosen to be sensible, so that the following commands should give satisfactory
results in most cases:
----
cjxl input.png output.jxl
cjxl input.jpg output.jxl
cjxl input.gif output.jxl
----
Options
-------
-h::
--help::
Displays the options that `cjxl` supports. On its own, it will only show
basic options. It can be combined with `-v` or `-v -v` to show increasingly
advanced options as well.
-v::
--verbose::
Increases verbosity. Can be repeated to increase it further, and also
applies to `--help`.
-d 'distance'::
--distance='distance'::
The preferred way to specify quality. It is specified in multiples of a
just-noticeable difference. That is, `-d 0` is mathematically lossless,
`-d 1` should be visually lossless, and higher distances yield denser and
denser files with lower and lower fidelity. Lossy sources such as JPEG and
GIF files are compressed losslessly by default, and in the case of JPEG
files specifically, the original JPEG can then be reconstructed bit-for-bit.
For lossless sources, `-d 1` is the default.
-q 'quality'::
--quality='quality'::
Alternative way to indicate the desired quality. 100 is lossless and lower
values yield smaller files. There is no lower bound to this quality
parameter, but positive values should approximately match the quality
setting of libjpeg.
-e 'effort'::
--effort='effort'::
Controls the amount of effort that goes into producing an ``optimal'' file
in terms of quality/size. That is to say, all other parameters being equal,
a higher effort should yield a file that is at least as dense and possibly
denser, and with at least as high and possibly higher quality.
+
Recognized effort settings, from fastest to slowest, are:
+
- 1 or ``lightning''
- 2 or ``thunder''
- 3 or ``falcon''
- 4 or ``cheetah''
- 5 or ``hare''
- 6 or ``wombat''
- 7 or ``squirrel'' (default)
- 8 or ``kitten''
- 9 or ``tortoise''
Examples
--------
----
# Compress a PNG file to a high-quality JPEG XL version.
$ cjxl input.png output.jxl
# Compress it at a slightly lower quality, appropriate for web use.
$ cjxl -d 2 input.png output.jxl
# Compress it losslessly. These are equivalent.
$ cjxl -d 0 input.png lossless.jxl
$ cjxl -q 100 input.png lossless.jxl
# Compress a JPEG file losslessly.
$ cjxl input.jpeg lossless-jpeg.jxl
----
See also
--------
*djxl*(1)

View File

@ -0,0 +1,61 @@
djxl(1)
=======
:doctype: manpage
Name
----
djxl - decompress JPEG XL images
Synopsis
--------
*djxl* ['options'...] 'input.jxl' ['output']
Description
-----------
`djxl` decompresses a JPEG XL image or animation. The output format is determined
by the extension of the output file, which can be `.png`, `.jpg`, `.ppm`, `.pfm`.
If the JPEG XL input file contains an animation, multiple output files will be
produced, with names of the form "'output'-*framenumber*.ext".
Options
-------
-h::
--help::
Displays the options that `djxl` supports.
-j::
--pixels_to_jpeg::
By default, if the input JPEG XL contains a recompressed JPEG file,
djxl reconstructs the exact original JPEG file if the output file has the
`.jpg` (or `.jpeg`) filename extension.
This flag causes the decoder to instead decode the image to pixels and
encode a new (lossy) JPEG in this case.
-q 'quality'::
--jpeg_quality='quality'::
When decoding to `.jpg`, use this output quality. This option implicitly
enables the --pixels_to_jpeg option.
Examples
--------
----
# Decompress a JPEG XL file to PNG
$ djxl input.jxl output.png
# Reconstruct a losslessly-recompressed JPEG file
$ djxl lossless-jpeg.jxl reconstructed.jpeg
----
See also
--------
*cjxl*(1)

314
third-party/libjxl/libjxl/doc/release.md vendored Normal file
View File

@ -0,0 +1,314 @@
# libjxl release process
This guide documents the release process for the libjxl project.
libjxl follows the [semantic versioning](https://semver.org/spec/v2.0.0.html)
specification for released versions. Releases are distributed as tags in the git
repository with the semantic version prefixed by the letter "v". For example,
release version "0.3.7" will have a git tag "v0.3.7".
The public API is explicitly defined as C headers in the `lib/include`
directory, normally installed in your include path. All other headers are
internal API and are not covered by the versioning rules.
## Development and release workflow
New code development is performed on the `main` branch of the git repository.
Pre-submit checks enforce minimum build and test requirements for new patches
that balance impact and test latency, but not all checks are performed before
pull requests are merged. Several slower checks only run *after* the code has
been merged to `main`, resulting in some errors being detected hours after the
code is merged or even days after in the case of fuzzer-detected bugs.
Release tags are cut from *release branches*. Each MAJOR.MINOR version has its
own release branch, for example releases `0.7.0`, `0.7.1`, `0.7.2`, ... would
have tags `v0.7.0`, `v0.7.1`, `v0.7.2`, ... on commits from the `v0.7.x` branch.
`v0.7.x` is a branch name, not a tag name, and doesn't represent a released
version since semantic versioning requires that the PATCH is a non-negative
number. Released tags don't each one have their own release branch, all releases
from the same MAJOR.MINOR version will share the same branch. The first commit
after the branch-off points between the main branch and the release branch
should be tagged with the suffix `-snapshot` and the name of the next
MAJOR.MINOR version, in order to get meaningful output for `git describe`.
The main purpose of the release branch is to stabilize the code before a
release. This involves including fixes to existing bugs but **not** including
new features. New features often come with new bugs which take time to fix, so
having a release branch allows us to cherry-pick *bug fixes* from the `main`
branch into the release branch without including the new *features* from `main`.
For this reason it is important to make small commits in `main` and separate bug
fixes from new features.
After the initial minor release (`MAJOR.MINOR.PATCH`, for example `0.5.0`) the
release branch is used to continue to cherry-pick fixes to be included in a
patch release, for example a version `0.5.1` release. Patch fixes are only meant
to fix security bugs or other critical bugs that can't wait until the next major
or minor release.
Release branches *may* continue to be maintained even after the next minor or
major version has been released to support users that can't update to a newer
minor release. In that case, the same process applies to all the maintained
release branches.
A release branch with specific cherry-picks from `main` means that the release
code is actually a version of the code that never existed in the `main` branch,
so it needs to be tested independently. Pre-submit and post-submit tests run on
release branches (branches matching `v*.*.x`) but extra manual checks should be
performed before a release, specially if multiple bug fixes interact with each
other. Take this into account when selecting which commits to include in a
release. The objective is to have a stable version that can be used without
problems for months. Having the latest improvements at the time the release tag
is created is a non-goal.
## Creating a release branch
A new release branch is needed before creating a new major or minor release,
that is, a new release where the MAJOR or MINOR numbers are increased. Patch
releases, where only the PATCH number is increased, reuse the branch from the
previous release of the same MAJOR and MINOR numbers.
The following instructions assume that you followed the recommended [libjxl git
setup](developing_in_github.md) where `origin` points to the upstream
libjxl/libjxl project, otherwise use the name of your upstream remote repository
instead of `origin`.
The release branch is normally created from the latest work in `main` at the
time the branch is created, but it is possible to create the branch from an
older commit if the current `main` is particularly unstable or includes commits
that were not intended to be included in the release. The following example
creates the branch `v0.5.x` from the latest commit in main (`origin/main`), if a
different commit is to be used then replace `origin/main` with the SHA of that
commit. Change the `v0.5.x` branch name to the one you are creating.
```bash
git fetch origin main
git push git@github.com:libjxl/libjxl.git origin/main:refs/heads/v0.5.x
```
Here we use the SSH URL explicitly since you are pushing to the `libjxl/libjxl`
project directly to a branch there. If you followed the guide `origin` will have
the HTTPS URL which wouldn't normally let you push since you wouldn't be
authenticated. The `v*.*.x` branches are [GitHub protected
branches](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches)
in our repository, however you can push to a protected branch when *creating* it
but you can't directly push to it after it is created. To include more changes
in the release branch see the "Cherry-picking fixes to a release" section below.
## Creating a merge label
We use GitHub labels in Pull Requests to keep track of the changes that should
be merged into a given release branch. For this purpose create a new label for
each new MAJOR.MINOR release branch called `merge-MAJOR.MINOR`, for example,
`merge-0.5`.
In the [edit labels](https://github.com/libjxl/libjxl/issues/labels) page, click
on "New label" and create the label. Pick your favorite color.
Labels are a GitHub-only concept and are not represented in git. You can add the
label to a Pull Request even after it was merged, whenever it is decided that
the Pull Request should be included in the given release branch. Adding the
label doesn't automatically merge it to the release branch.
## Update the versioning number
The version number (as returned by `JxlDecoderVersion`) in the source code in
`main` must match the semantic versioning of a release. After the release
branch is created the code in `main` will only be included in the next major
or minor release. Right after a release branch update the version targeting the
next release. Artifacts from `main` should include the new (unreleased) version,
so it is important to update it. For example, after the `v0.5.x` branch is
created from main, you should update the version on `main` to `0.6.0`.
To help update it, run this helper command (in a Debian-based system):
```bash
./ci.sh bump_version 0.6.0
```
This will update the version in the following files:
* `lib/CMakeLists.txt`
* `lib/lib.gni`, automatically updated with
`tools/scripts/build_cleaner.py --update`.
* `debian/changelog` to create the Debian package release with the new version.
Debian changelog shouldn't repeat the library changelog, instead it should
include changes to the packaging scripts.
* `.github/workflows/conformance.yml`
If there were incompatible API/ABI changes, make sure to also adapt the
corresponding section in
[CMakeLists.txt](https://github.com/libjxl/libjxl/blob/main/lib/CMakeLists.txt#L12).
## Cherry-pick fixes to a release
After a Pull Request that should be included in a release branch has been merged
to `main` it can be cherry-picked to the release branch. Before cherry-picking a
change to a release branch it is important to check that it doesn't introduce
more problems, in particular it should run for some time in `main` to make sure
post-submit tests and the fuzzers run on it. Waiting for a day is a good idea.
Most of the testing is done on the `main` branch, so be careful with what
commits are cherry-picked to a branch. Refactoring code is often not a good
candidate to cherry-pick.
To cherry-pick a single commit to a release branch (in this example to `v0.5.x`)
you can run:
```bash
git fetch origin
git checkout origin/v0.5.x -b merge_to_release
git cherry-pick -x SHA_OF_MAIN_COMMIT
# -x will annotate the cherry-pick with the original SHA_OF_MAIN_COMMIT value.
# If not already mentioned in the original commit, add the original PR number to
# the commit, for example add "(cherry picked from PR #NNNN)".
git commit --amend
```
The `SHA_OF_MAIN_COMMIT` is the hash of the commit as it landed in main. Use
`git log origin/main` to list the recent main commits and their hashes.
Making sure that the commit message on the cherry-picked commit contains a
reference to the original pull request (like `#NNNN`) is important. It creates
an automatic comment in the original pull request notifying that it was
mentioned in another commit, helping keep track of the merged pull requests. If
the original commit was merged with the "Squash and merge" policy it will
automatically contain the pull request number on the first line, if this is not
the case you can amend the commit message of the cherry-pick to include a
reference.
Multiple commits can be cherry-picked and tested at once to save time. Continue
running `git cherry-pick` and `git commit --amend` multiple times for all the
commits you need to cherry-pick, ideally in the same order they were merged on
the `main` branch. At the end you will have a local branch with multiple commits
on top of the release branch.
To update the version number, for example from v0.8.0 to v0.8.1 run this helper
command (in a Debian-based system):
```bash
./ci.sh bump_version 0.8.1
```
as described above and commit the changes.
Finally, upload your changes to *your fork* like normal, except that when
creating a pull request select the desired release branch as a target:
```bash
git push myfork merge_to_release
```
If you used the [guide](developing_in_github.md) `myfork` would be `origin` in
that example. Click on the URL displayed, which will be something like
`https://github.com/mygithubusername/libjxl/pull/new/merge_to_release`
In the "Open a pull request" page, change the drop-down base branch from
"base: main" (the default) to the release branch you are targeting.
The pull request approval and pre-submit rules apply as with normal pull
requests to the `main` branch.
**Important:** When merging multiple cherry-picks use "Rebase and merge" policy,
not the squash one since otherwise you would discard the individual commit
message references from the git history in the release branch.
## Publishing a release
Once a release tag is created it must not be modified, so you need to prepare
the changes before creating the release. Make sure you checked the following:
* The semantic version number in the release branch (see `lib/CMakeLists.txt`)
matches the number you intend to release, all three MAJOR, MINOR and PATCH
should match. Otherwise send a pull request to the release branch to
update them.
* The GitHub Actions checks pass on the release branch. Look for the green
tick next to the last commit on the release branch. This should be visible
on the branch page, for example: https://github.com/libjxl/libjxl/tree/v0.5.x
* There no open fuzzer-found bugs for the release branch. The most effective
way is to [run the fuzzer](fuzzing.md) on the release branch for a while. You
can seed the fuzzer with corpus generated by oss-fuzz by [downloading
it](https://google.github.io/oss-fuzz/advanced-topics/corpora/#downloading-the-corpus),
for example `djxl_fuzzer` with libFuzzer will use:
gs://libjxl-corpus.clusterfuzz-external.appspot.com/libFuzzer/libjxl_djxl_fuzzer
* Manually check that images encode/decode ok.
* Manually check that downstream projects compile with our code. Sometimes
bugs on build scripts are only detected when other projects try to use our
library. For example, test compiling
[imagemagick](https://github.com/ImageMagick/ImageMagick) and Chrome.
A [GitHub
"release"](https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/about-releases)
consists of two different concepts:
* a git "tag": this is a name (`v` plus the semantic version number) with a
commit hash associated, defined in the git repository. Most external projects
will use git tags or HTTP URLs to these tags to fetch the code.
* a GitHub "release": this is a GitHub-only concept and is not represented in
git other than by having a git tag associated with the release. A GitHub
release has a given source code commit SHA associated (through the tag) but
it *also* contains release notes and optional binary files attached to the
release.
Releases from the older GitLab repository only have a git tag in GitHub, while
newer releases have both a git tag and a release entry in GitHub.
To publish a release open the [New Release
page](https://github.com/libjxl/libjxl/releases/new) and follow these
instructions:
* Set the "Tag version" as "v" plus the semantic version number.
* Select the "Target" as your release branch. For example for a "v0.7.1"
release tag you should use the "v0.7.x" branch.
* Use the version number as the release title.
* Copy-paste the relevant section of the [CHANGELOG.md](../CHANGELOG.md) to the
release notes into the release notes. Add any other information pertaining
the release itself that are not included in the CHANGELOG.md, although prefer
to include those in the CHANGELOG.md file. You can switch to the Preview tab
to see the results.
* Finally click "Publish release" and go celebrate with the team. 🎉
* Make sure to manually push the commit of the release also to https://gitlab.com/wg1/jpeg-xl.
### How to build downstream projects
```bash
docker run -it debian:bullseye /bin/bash
apt update
apt install -y clang cmake git libbrotli-dev nasm pkg-config ninja-build
export CC=clang
export CXX=clang++
git clone --recurse-submodules --depth 1 -b v0.7.x \
https://github.com/libjxl/libjxl.git
git clone --recurse-submodules --depth 1 \
https://github.com/ImageMagick/ImageMagick.git
git clone --recurse-submodules --depth 1 \
https://github.com/FFmpeg/FFmpeg.git
cd ~/libjxl
git checkout v0.7.x
cmake -B build -G Ninja .
cmake --build build
cmake --install build
cd ~/ImageMagick
./configure --with-jxl=yes
# check for "JPEG XL --with-jxl=yes yes"
make -j 80
cd ~/FFmpeg
./configure --enable-libjxl
# check for libjxl decoder/encoder support
make -j 80
```

View File

@ -0,0 +1,75 @@
# JPEG XL software support
This document attempts to keep track of software that is using libjxl to support JPEG XL.
This list serves several purposes:
- thank/acknowledge other projects for integrating jxl support
- point end-users to software that can read/write jxl
- keep track of the adoption status of jxl
- in case of a (security) bug in libjxl, it's easier to see who might be affected and check if they are updated (in case they use static linking)
Please add missing software to this list.
## Browsers
- Chromium: behind a flag from version 91 to 109, [tracking bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1178058)
- Firefox: behind a flag since version 90, [tracking bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1539075)
- Safari: supported since version 17 beta [release notes](https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes), [tracking bug](https://bugs.webkit.org/show_bug.cgi?id=208235)
- Edge: behind a flag since version 91, start with `.\msedge.exe --enable-features=JXL`
- Opera: behind a flag since version 77.
- Basilisk: supported since version v2023.01.07, [release notes](https://www.basilisk-browser.org/releasenotes.shtml)
- Pale Moon: supported since version 31.4.0, [release notes](https://www.palemoon.org/releasenotes-archived.shtml#v31.4.0)
- Waterfox: [enabled by default](https://github.com/WaterfoxCo/Waterfox/pull/2936)
For all browsers and to track browsers progress see [Can I Use](https://caniuse.com/jpegxl).
## Image libraries
- [ImageMagick](https://imagemagick.org/): supported since 7.0.10-54
- [libvips](https://libvips.github.io/libvips/): supported since 8.11
- [Imlib2](https://github.com/alistair7/imlib2-jxl)
- [FFmpeg](https://github.com/FFmpeg/FFmpeg/search?q=jpeg-xl&type=commits)
- [GDAL](https://gdal.org/drivers/raster/jpegxl.html): supported since 3.4.0 as a TIFF codec, and 3.6.0 as standalone format
- [GraphicsMagick](http://www.graphicsmagick.org/NEWS.html#march-26-2022): supported since 1.3.38
## OS-level support / UI frameworks / file browser plugins
- Qt / KDE: [plugin available](https://github.com/novomesk/qt-jpegxl-image-plugin)
- GDK-pixbuf: plugin available in libjxl repo
- [gThumb](https://ubuntuhandbook.org/index.php/2021/04/gthumb-3-11-3-adds-jpeg-xl-support/)
- [MacOS viewer/QuickLook plugin](https://github.com/yllan/JXLook)
- [Windows Imaging Component](https://github.com/mirillis/jpegxl-wic)
- [Windows thumbnail handler](https://github.com/saschanaz/jxl-winthumb)
- [OpenMandriva Lx (since 4.3 RC)](https://www.openmandriva.org/en/news/article/openmandriva-lx-4-3-rc-available-for-testing)
- [KaOS (since 2021.06)](https://news.itsfoss.com/kaos-2021-06-release/)
- [EFL (since 1.27, no external plugin needed)](https://www.enlightenment.org)
## Image editors
- [Adobe Camera Raw (since version 15)](https://helpx.adobe.com/camera-raw/using/hdr-output.html)
- [Affinity (since V2)](https://affinity.serif.com/en-gb/whats-new/)
- [darktable (since 4.2)](https://github.com/darktable-org/darktable/releases/tag/release-4.2.0)
- [GIMP (since 2.99.8)](https://www.gimp.org/news/2021/10/20/gimp-2-99-8-released/); plugin for older versions available in libjxl repo
- [Graphic Converter (since 11.5)](https://www.lemkesoft.de/en/products/graphicconverter/)
- [Krita](https://invent.kde.org/graphics/krita/-/commit/13e5d2e5b9f0eac5c8064b7767f0b62264a0797b)
- [Paint.NET](https://www.getpaint.net/index.html); supported since 4.3.12 - requires a [plugin](https://github.com/0xC0000054/pdn-jpegxl) to be downloaded and installed.
- Photoshop: no plugin available yet, no official support yet
## Image viewers
- [XnView](https://www.xnview.com/en/)
- [ImageGlass](https://imageglass.org/)
- [IrfanView](https://www.irfanview.com/); supported since 4.59 - requires a [plugin](https://www.irfanview.com/plugins.htm) to be downloaded and enabled.
- [Tachiyomi](https://github.com/tachiyomiorg/tachiyomi/releases/tag/v0.12.1)
- Any viewer based on Qt, KDE, GDK-pixbuf, EFL, ImageMagick, libvips or imlib2 (see above)
- Qt viewers: gwenview, digiKam, KolourPaint, KPhotoAlbum, LXImage-Qt, qimgv, qView, nomacs, VookiImageViewer, PhotoQt
- GTK viewers: Eye of Gnome (eog), gThumb, Geeqie
- EFL viewers: entice, ephoto
- [Swayimg](https://github.com/artemsen/swayimg)
## Online tools
- [Squoosh](https://squoosh.app/)
- [Cloudinary](https://cloudinary.com/blog/cloudinary_supports_jpeg_xl)
- [MConverter](https://mconverter.eu/)
- [jpegxl.io](https://jpegxl.io/)

View File

@ -0,0 +1,15 @@
API reference
=============
``libjxl`` exposes a C API for encoding and decoding JPEG XL files with some
C++ header-only helpers for C++ users.
.. toctree::
:caption: API REFERENCE
:maxdepth: 2
api_decoder
api_encoder
api_common
api_butteraugli
api_threads

View File

@ -0,0 +1,6 @@
Butteraugli API - ``jxl/butteraugli.h``
=======================================
.. doxygengroup:: libjxl_butteraugli
:members:
:private-members:

View File

@ -0,0 +1,6 @@
Common API concepts
===================
.. doxygengroup:: libjxl_common
:members:
:private-members:

View File

@ -0,0 +1,6 @@
Decoder API - ``jxl/decode.h``
==============================
.. doxygengroup:: libjxl_decoder
:members:
:private-members:

View File

@ -0,0 +1,6 @@
Encoder API - ``jxl/encode.h``
==============================
.. doxygengroup:: libjxl_encoder
:members:
:private-members:

View File

@ -0,0 +1,6 @@
Multi-threaded Encoder/Decoder
==============================
.. doxygengroup:: libjxl_threads
:members:
:private-members:

View File

@ -0,0 +1,110 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Configuration file for the Sphinx documentation builder.
#
# See https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import re
import subprocess
def GetVersion():
"""Function to get the version of the current code."""
with open(os.path.join(
os.path.dirname(__file__), '../../lib/CMakeLists.txt'), 'r') as f:
cmakevars = {}
for line in f:
m = re.match(r'set\(JPEGXL_([A-Z]+)_VERSION ([^\)]+)\)', line)
if m:
cmakevars[m.group(1)] = m.group(2)
return '%s.%s.%s' % (cmakevars['MAJOR'], cmakevars['MINOR'], cmakevars['PATCH'])
def ConfigProject(app, config):
# Configure the doxygen xml directory as the "xml" directory next to the
# sphinx output directory. Doxygen generates by default the xml files in a
# "xml" sub-directory of the OUTPUT_DIRECTORY.
build_dir = os.path.dirname(app.outdir)
xml_dir = os.path.join(build_dir, 'xml')
config.breathe_projects['libjxl'] = xml_dir
# Read the docs build environment doesn't run our cmake script so instead we
# need to run doxygen manually here.
if os.environ.get('READTHEDOCS', None) != 'True':
return
root_dir = os.path.realpath(os.path.join(app.srcdir, '../../'))
doxyfile = os.path.join(build_dir, 'Doxyfile-rtd.doc')
with open(doxyfile, 'w') as f:
f.write(f"""
FILE_PATTERNS = *.c *.h
GENERATE_HTML = NO
GENERATE_LATEX = NO
GENERATE_XML = YES
INPUT = lib/include doc/api.txt
OUTPUT_DIRECTORY = {build_dir}
PROJECT_NAME = LIBJXL
QUIET = YES
RECURSIVE = YES
STRIP_FROM_PATH = lib/include
WARN_AS_ERROR = YES
""")
subprocess.check_call(['doxygen', doxyfile], cwd=root_dir)
def setup(app):
# Generate doxygen XML on init when running from Read the docs.
app.connect("config-inited", ConfigProject)
### Project information
project = 'libjxl'
project_copyright = 'JPEG XL Project Authors'
author = 'JPEG XL Project Authors'
version = GetVersion()
### General configuration
extensions = [
# For integration with doxygen documentation.
'breathe',
# sphinx readthedocs theme.
'sphinx_rtd_theme',
# Do we use it?
'sphinx.ext.graphviz',
]
breathe_default_project = 'libjxl'
breathe_projects = {}
# All the API is in C, except those files that end with cxx.h.
breathe_domain_by_extension = {'h': 'cpp'}
breathe_domain_by_file_pattern = {
'*cxx.h': 'cpp',
}
breathe_implementation_filename_extensions = ['.cc']
# These are defined at build time by cmake.
c_id_attributes = [
'JXL_EXPORT',
'JXL_DEPRECATED',
'JXL_THREADS_EXPORT',
]
cpp_id_attributes = c_id_attributes
breathe_projects_source = {
'libjxl' : ('../../', [
'doc/api.txt',
'lib/include/jxl',
])
}
# Recognized suffixes.
source_suffix = ['.rst', '.md']
### Options for HTML output
# Use the readthedocs.io theme when generating the HTML output.
html_theme = 'sphinx_rtd_theme'

View File

@ -0,0 +1,18 @@
.. libjxl sphinx documentation entrypoint
JPEG XL image format reference implementation
=============================================
.. toctree::
:maxdepth: 3
:caption: Contents:
api
Indices and tables
==================
* :ref:`genindex`
* :ref:`search`

View File

@ -0,0 +1,3 @@
breathe
sphinx
sphinx-rtd-theme

View File

@ -0,0 +1,245 @@
# Security Vulnerabilities Playbook
## Reporting security bugs
Report security bugs by emailing libjxl-security@google.com.
Don't open a GitHub issue, don't discuss it public forums like Discord and don't
send a Pull Request if you think you have found a security bug.
## Overview
This document outlines the guidelines followed by the project when handling
security bugs, their fixes, disclosure and coordination with security
researchers. For more context about this guide, read the [coordinated
vulnerability disclosure
guidelines](https://github.com/google/oss-vulnerability-guide/blob/main/guide.md)
from Google Open Source Programs Office.
The main target audience of this guide is the coordinator from the libjxl
Vulnerability Management Team (VMT) handling the requests, however it is useful
for other people to understand what to expect from this process.
Members of the VMT monitor the reports received by email and will coordinate
for these to be addressed. This doesn't mean that said member would fix the bug,
but their responsibility is to make sure it is handled properly according to
this guide.
## Life of security bug
The Coordinator from VMT will make sure that the following steps are taken.
1. Acknowledge the bug report.
Our policy mandates a maximum of **3 business days** to respond to bug reports
in the given email, but you should respond as soon as possible and keep a fluid
communication with the reporter, who has spent some time looking at the issue.
2. Determine if the bug is a security bug covered by our policy.
Not all bugs are security bugs, and not all security bugs are covered by this
vulnerability disclosure policy. See the [What's a Security bug] section below.
3. Determine the affected versions.
Often new bugs on stable projects are found on new features or because of those
new features, so only the most recent versions are affected. It is important to
determine both what older versions are affected, so users running those older
versions can patch or update the software, and also what older versions are
*not* affected. It is possible that stable distributions ship older versions
that didn't contain the bug and therefore don't need to patch the code. Often
maintainers of package distributions need to patch older versions instead of
updating due to incompatibilities with newer ones and they need to understand
what's the vulnerable code.
Security bugs that have already been fixed in `main` or in already released code
but not disclosed as a vulnerability, for example if fixed as a result of a
refactor, should be treated like any other security bug in this policy and
disclosed indicating the range of older affected versions (expect for versions
before 0.5, see below). In such case a new release would likely not be needed if
one already exists, but stable distributions may be still using those version
and need to be aware of the issue and fix.
If no released version is affected by the bug, for example because it was only
introduced in the `main` branch but not yet released, then no vulnerability
disclosure is needed.
Note: Versions before 0.5 are not covered by the security policy. Those versions
have multiple security issues and should not be used anyway.
4. Communicate with the reporter
Communicate the decision to the reporter.
If the bug was not considered a security bug or not covered by this policy,
explain why and direct the reporter to open a public [issue in
GitHub](https://github.com/libjxl/libjxl/issues) or open one on their behalf.
You don't need to follow the rest of the guide in this case.
If the bug *is* a covered security bug then follow the rest of this guide.
Ask the reporter how they want to be credited in the disclosure: name and
company affiliation if any. Security researchers often value this recognition
and helps them dedicate their time to finding security bugs in our project.
There's no bug bounty (monetary compensation for security bugs) available for
libjxl.
5. Create a Security Advisory draft in GitHub
At this point it was established that the bug is a security issue that requires
a vulnerability disclosure. Start by creating a Security Advisory draft in the
[Security Advisories](https://github.com/libjxl/libjxl/security/advisories) page
in GitHub.
Add a short description of the bug explaining what's the issue and what's the
impact of the issue. Being 'hard' or 'complex' to exploit is not a reason to
discard the potential impact. You can update this description later, save it as
a draft in GitHub.
Add the reporter to the security advisory draft if they have a GitHub account,
and add the project members that will be working on a fix for the bug.
Establish the severity of the issue according to the impact and tag the
appropriate Common Weakness Enumeration (CWE) values. This helps classify the
security issues according to their nature.
6. Work on a fix in a private branch
Coordinators can work on the fix themselves, use a proposed fix from the
reporter if there is one, or work with other project members to create one.
Work on a fix for the bug in *private*. Don't publish a Pull Request with the
fix like you normally do, and don't upload the fix to your libjxl fork. If you
ask another project member to work on it, explain them that they should follow
this guide.
7. Request a CVE number
The Common Vulnerabilities and Exposures (CVE) is the system used to disclose
vulnerabilities in software. A CVE number, like CVE-2021-NNNNNN, is a unique
identifier for a given vulnerability. These numbers are assigned by a CVE
Numbering Authority (CNA) with scope on the given project that has the
vulnerability. For libjxl, we use Google's Generic CNA.
For VMT coordinators at Google, file a bug at
[go/cve-request](https://goto.google.com/cve-request) to request a CVE. See
go/vcp-cna for context.
When requesting the CVE include:
* A description of the problem (example: bug when parsing this field)
* A description of the impact of the bug (example: OOB read, remote code
execution, etc)
* The proposed CWE id(s) determined earlier.
* List of affected versions.
* Reporter of the bug and their preferred name/company to include in the
disclosure.
* Links to the issues/fixes (if already public), these can be added later, even
after the CVE is public.
* The CPE prefix of the affected project (`cpe:2.3:a:libjxl_project:libjxl`)
When in doubt, you can discuss these with the security team while requesting it.
8. File a Security bug in Chromium (if affected).
libjxl project is in charge of updating and maintaining Chromium's libjxl
integration code, this includes updating the libjxl library when needed. While
the regular CVE disclosure process will eventually create a bug to update
Chromium, filing one at this stage speeds up the process.
[go/crbug](https://goto.google.com/crbug), select the "Security Bug" template
and complete the details. This bug will be used to keep track of what versions
of Chromium need backporting. The new bug in Chromium will not be public
initially, but will be made public some time after the issue is fixed.
9. Test the fixes on the intended releases
When disclosing a vulnerability normally two ways to fix it are offered:
* A patch or set of patches that fix the issue on `main` branch, and
* A new release that contains the security fix for the user to update to.
New releases that fix the vulnerability should be PATCH releases, that is, a
previous release (like 1.2.3) plus the patches that fix the vulnerability,
becoming a new version (like 1.2.4). See the [release process](release.md) for
details. At least the latest MINOR release branch should have a PATCH release
with the fix, however it might make sense to also backport the fix to older
minor branch releases, depending on long-term support schedule for certain
releases. For example, if many users are still using a particular older version
of the library and updating to a new version requires significant changes (due
to a redesigned API or new unavailable dependencies) it is helpful to provide a
PATCH release there too.
In either case, make sure that you test the fix in all the branches that you
intend to release it to.
The Continuous Integration pipelines don't work on the private forks created by
the Security Advisory, so manual testing of the fix is needed there before
making it public. Don't upload it to your public fork for testing.
10. Coordinate a date for release of the vulnerability disclosure.
Agree with the reporter and security folks from the CNA on a release date. There
is a maximum of 90 day disclosure timeline from the day the bug was reported.
On the disclosure date publish the fixes and tag the new PATCH release with the
fix. You can prepare private drafts of the release for review beforehand to
reduce the workload.
Update Chromium to the new release version (if affected) and work with Chrome
engineers on the required backports.
## What's a Security bug
A security bug is a bug that can potentially be exploited to let an attacker
gain unauthorized access or privileges. For example, gaining code execution in
libjxl decoder by decoding a malicious .jxl file is a security but hitting a
`JXL_ASSERT()` is not necessarily one.
The supported use cases to consider in the context of security bugs that require
a vulnerability disclosure are "release" builds. The disclosure is intended for
users of the project, to let them know that there is a security issue and that
they should update or patch it.
Unreleased versions are not relevant in this context. A bug introduced in the
`main` branch that is not yet in any release is not covered by this guide even
if the bug allows a remote code execution. CVEs should have a non-empty list of
affected released versions.
"Developer only" code is also not covered by this policy. In particular, tools
that are not installed by the build, or not installed when packaging `libjxl`
are not covered. For example, a bug in `tone_map` would not affect users since
is a developer-only tool. The rationale behind this is that users of the
released software will not have the developer code. This developer code is in
the same libjxl repository for convenience.
When considering the impact of a bug, "release" mode should be assumed. In
release mode `JXL_ASSERT()` and `JXL_CHECK()` are enabled, but `JXL_DASSERT()`
are not. This means that if a `JXL_DASSERT()` protects an out-of-bounds (OOB)
write, then the impact of a bug hitting the `JXL_DASSERT()` is at least an
OOB write. On the other hand, if a bug ends up hitting a `JXL_CHECK()` instead
of continuing, the only impact is the process abort instead of whatever else is
possible after the `JXL_CHECK()`.
Asserts in `libjxl` *tools* cause the tool process to abort, but don't affect
the caller. Either crashing or returning an error (non-zero exit code) would
have the same effect, so `JXL_ASSERT()` failures in the tools have no security
or functional impact.
Asserts in `libjxl` libraries, meant to be linked into other processes, cause
the caller process to abort, potentially causing a Denial of Service, however,
Denial of Service issues are *not* considered security bugs by this policy.
These are still issues and should be fixed, but they are not security issues.
Out-of-bounds (OOB) reads in process memory are considered security
vulnerabilities. OOB reads may allow an attacker to read other buffers from the
same process that it shouldn't have access to, even a small OOB read can
allow the attacker to read an address in the stack or in the heap, defeating
address space randomization techniques. In combination with other bugs these
can enable or simplify attacks to the process using libjxl. OOB reads don't need
to require a segmentation fault to be a problem, leaking process information in
decoded RGB pixels could be used as part of an exploit in some scenarios.
OOB writes and remote code execution (RCE) are security bugs of at least high
security impact.

View File

@ -0,0 +1,181 @@
# XL Overview
## Requirements
JPEG XL was designed for two main requirements:
* high quality: visually lossless at reasonable bitrates;
* decoding speed: multithreaded decoding should be able to reach around
400 Megapixel/s on large images.
These goals apply to various types of images, including HDR content, whose
support is made possible by full-precision (float32) computations and extensive
support of color spaces and transfer functions.
High performance is achieved by designing the format with careful consideration
of memory bandwidth usage and ease of SIMD/GPU implementation.
The full requirements for JPEG XL are listed in document wg1m82079.
## General architecture
The architecture follows the traditional block transform model with improvements
in the individual components. For a quick overview, we sketch a "block diagram"
of the lossy format decoder in the form of module names in **bold** followed by
a brief description. Note that post-processing modules in [brackets] are
optional - they are unnecessary or even counterproductive at very high quality
settings.
**Header**: decode metadata (e.g. image dimensions) from compressed fields
(smaller than Exp-Golomb thanks to per-field encodings). The compression and
small number of required fields enables very compact headers - much smaller than
JFIF and HEVC. The container supports multiple images (e.g. animations/bursts)
and passes (progressive).
**Bitstream**: decode transform coefficient residuals using rANS-encoded
<#bits,bits> symbols
**Dequantize**: from adaptive quant map side information, plus chroma from luma
**DC prediction**: expand DC residuals using adaptive (history-based) predictors
**Chroma from luma**: restore predicted X from B and Y from B
**IDCT:** 2x2..32x32, floating-point
**[Gaborish]**: additional deblocking convolution with 3x3 kernel
**[Edge preserving filter]**: nonlinear adaptive smoothing controlled by side
information
**[Noise injection]**: add perceptually pleasing noise according to a per-image
noise model
**Color space conversion**: from perceptual opsin XYB to linear RGB
**[Converting to other color spaces via ICC]**
The encoder is basically the reverse:
**Color space conversion**: from linear RGB to perceptual opsin XYB
**[Noise estimation]**: compute a noise model for the image
**[Gaborish]**: sharpening to counteract the blurring on the decoder side
**DCT**: transform sizes communicated via per-block side information
**Chroma from luma**: find the best multipliers of Y for X and B channels of
entire image
**Adaptive quantization**: iterative search for quant map that yields the best
perceived restoration
**Quantize**: store 16-bit prediction residuals
**DC prediction**: store residuals (prediction happens in quantized space)
**Entropy coding**: rANS and context modeling with clustering
# File Structure
A codestream begins with a `FileHeader` followed by one or more "passes"
(= scans: e.g. DC or AC_LF) which are then added together (summing the
respective color components in Opsin space) to form the final image. There is no
limit to the number of passes, so an encoder could choose to send salient parts
first, followed by arbitrary decompositions of the final image (in terms of
resolution, bit depth, quality or spatial location).
Each pass contains groups of AC and DC data. A group is a subset of pixels that
can be decoded in parallel. DC groups contain 256x256 DCs (from 2048x2048 input
pixels), AC groups cover 256x256 input pixels.
Each pass starts with a table of contents (sizes of each of their DC+AC
groups), which enables parallel decoding and/or the decoding of a subset.
However, there is no higher-level TOC of passes, as that would prevent
appending additional images and could be too constraining for the encoder.
## Lossless
JPEG XL supports tools for lossless coding designed by Alexander Rhatushnyak and
Jon Sneyers. They are about 60-75% of size of PNG, and smaller than WebP
lossless for photos.
An adaptive predictor computes 4 from the NW, N, NE and W pixels and combines
them with weights based on previous errors. The error value is encoded in a
bucket chosen based on a heuristic max error. The result is entropy-coded using
the ANS encoder.
## Current Reference Implementation
### Conventions
The software is written in C++ and built using CMake 3.6 or later.
Error handling is done by having functions return values of type `jxl::Status`
(a thin wrapper around bool which checks that it is not ignored). A convenience
macro named `JXL_RETURN_IF_ERROR` makes this more convenient by automatically
forwarding errors, and another macro named `JXL_FAILURE` exits with an error
message if reached, with no effect in optimized builds.
To diagnose the cause of encoder/decoder failures (which often only result in a
generic "decode failed" message), build using the following command:
```bash
CMAKE_FLAGS="-DJXL_CRASH_ON_ERROR" ./ci.sh opt
```
In such builds, the first JXL_FAILURE will print a message identifying where the
problem is and the program will exit immediately afterwards.
### Architecture
Getting back to the earlier block diagram:
**Header** handling is implemented in `headers.h` and `field*`.
**Bitstream**: `entropy_coder.h`, `dec_ans_*`.
**(De)quantize**: `quantizer.h`.
**DC prediction**: `predictor.h`.
**Chroma from luma**: `chroma_from_luma.h`
**(I)DCT**: `dct*.h`. Instead of operating directly on blocks of memory, the
functions operate on thin wrappers which can handle blocks spread across
multiple image lines.
**DCT size selection**: `ac_strategy.cc`
**[Gaborish]**: `enc_gaborish.h`.
**[Edge preserving filter]**: `epf.h`
**[Noise injection]**: `noise*` (currently disabled)
**Color space conversion**: `color_*`, `dec_xyb.h`.
## Decoder overview
After decoding headers, the decoder begins processing frames (`dec_frame.cc`).
For each pass, it will read the DC group table of contents (TOC) and start
decoding, dequantizing and restoring color correlation of each DC group
(covering 2048x2048 pixels in the input image) in parallel
(`compressed_dc.cc`). The DC is split into parts corresponding to each AC group
(with 1px of extra border); the AC group TOC is read and each AC group (256x256
pixels) is processed in parallel (`dec_group.cc`).
In each AC group, the decoder reads per-block side information indicating the
kind of DCT transform; this is followed by the quantization field. Then, AC
coefficients are read, dequantized and have color correlation restored on a
tile per tile basis for better locality.
After all the groups are read, postprocessing is applied: Gaborish smoothing
and edge preserving filter, to reduce blocking and other artifacts.
Finally, the image is converted back from the XYB color space
(`dec_xyb.cc`) and saved to the output image (`codec_*.cc`).

View File

@ -0,0 +1,56 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Example project using libjxl.
cmake_minimum_required(VERSION 3.10)
project(SAMPLE_LIBJXL LANGUAGES C CXX)
# Use pkg-config to find libjxl.
find_package(PkgConfig)
pkg_check_modules(Jxl REQUIRED IMPORTED_TARGET libjxl)
pkg_check_modules(JxlThreads REQUIRED IMPORTED_TARGET libjxl_threads)
# Build the example encoder/decoder binaries using the default shared libraries
# installed.
add_executable(decode_oneshot decode_oneshot.cc)
target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
add_executable(decode_progressive decode_progressive.cc)
target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads)
add_executable(encode_oneshot encode_oneshot.cc)
target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
# Building a static binary with the static libjxl dependencies. How to load
# static library configs from pkg-config and how to build static binaries
# depends on the platform, and building static binaries in general has problems.
# If you don't need static binaries you can remove this section.
add_library(StaticJxl INTERFACE IMPORTED GLOBAL)
set_target_properties(StaticJxl PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${Jxl_STATIC_INCLUDE_DIR}"
INTERFACE_COMPILE_OPTIONS "${Jxl_STATIC_CFLAGS_OTHER}"
INTERFACE_LINK_LIBRARIES "${Jxl_STATIC_LDFLAGS}"
)
add_library(StaticJxlThreads INTERFACE IMPORTED GLOBAL)
set_target_properties(StaticJxlThreads PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${JxlThreads_STATIC_INCLUDE_DIR}"
INTERFACE_COMPILE_OPTIONS "${JxlThreads_STATIC_CFLAGS_OTHER}"
# libgcc uses weak symbols for pthread which means that -lpthread is not
# linked when compiling a static binary. This is a platform-specific fix for
# that.
INTERFACE_LINK_LIBRARIES
"${JxlThreads_STATIC_LDFLAGS} -Wl,--whole-archive -lpthread -Wl,--no-whole-archive"
)
add_executable(decode_oneshot_static decode_oneshot.cc)
target_link_libraries(decode_oneshot_static
-static StaticJxl StaticJxlThreads)
add_executable(encode_oneshot_static encode_oneshot.cc)
target_link_libraries(encode_oneshot_static
-static StaticJxl StaticJxlThreads)

View File

@ -0,0 +1,172 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This C++ example decodes a JPEG XL image in one shot (all input bytes
// available at once). The example outputs the pixels and color information to a
// floating point image and an ICC profile on disk.
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <vector>
bool DecodeJpegXlExif(const uint8_t* jxl, size_t size,
std::vector<uint8_t>* exif) {
auto dec = JxlDecoderMake(nullptr);
// We're only interested in the Exif boxes in this example, so don't
// subscribe to events related to pixel data.
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BOX)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
return false;
}
bool support_decompression = true;
if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
fprintf(stderr,
"NOTE: decompressing brob boxes not supported with the currently "
"used jxl library.\n");
support_decompression = false;
}
JxlDecoderSetInput(dec.get(), jxl, size);
JxlDecoderCloseInput(dec.get());
const constexpr size_t kChunkSize = 65536;
size_t output_pos = 0;
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
fprintf(stderr, "Decoder error\n");
return false;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
fprintf(stderr, "Error, already provided all input\n");
return false;
} else if (status == JXL_DEC_BOX) {
if (!exif->empty()) {
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
exif->resize(exif->size() - remaining);
// No need to wait for JXL_DEC_SUCCESS or decode other boxes.
return true;
}
JxlBoxType type;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetBoxType(dec.get(), type, support_decompression)) {
fprintf(stderr, "Error, failed to get box type\n");
return false;
}
if (!memcmp(type, "Exif", 4)) {
exif->resize(kChunkSize);
JxlDecoderSetBoxBuffer(dec.get(), exif->data(), exif->size());
}
} else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
output_pos += kChunkSize - remaining;
exif->resize(exif->size() + kChunkSize);
JxlDecoderSetBoxBuffer(dec.get(), exif->data() + output_pos,
exif->size() - output_pos);
} else if (status == JXL_DEC_SUCCESS) {
if (!exif->empty()) {
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
exif->resize(exif->size() - remaining);
return true;
}
return true;
} else {
fprintf(stderr, "Unknown decoder status\n");
return false;
}
}
}
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
FILE* file = fopen(filename, "rb");
if (!file) {
return false;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return false;
}
long size = ftell(file);
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
return false;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return false;
}
out->resize(size);
size_t readsize = fread(out->data(), 1, size, file);
if (fclose(file) != 0) {
return false;
}
return readsize == static_cast<size_t>(size);
}
bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Could not open %s for writing", filename);
return false;
}
fwrite(data, 1, size, file);
if (fclose(file) != 0) {
return false;
}
return true;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
fprintf(stderr,
"Usage: %s <jxl> <exif>\n"
"Where:\n"
" jxl = input JPEG XL image filename\n"
" exif = output exif filename\n"
"Output files will be overwritten.\n",
argv[0]);
return 1;
}
const char* jxl_filename = argv[1];
const char* exif_filename = argv[2];
std::vector<uint8_t> jxl;
if (!LoadFile(jxl_filename, &jxl)) {
fprintf(stderr, "couldn't load %s\n", jxl_filename);
return 1;
}
std::vector<uint8_t> exif;
if (!DecodeJpegXlExif(jxl.data(), jxl.size(), &exif)) {
fprintf(stderr, "Error while decoding the jxl file\n");
return 1;
}
if (exif.empty()) {
printf("No exif data present in this image\n");
} else {
// TODO(lode): the exif box data contains the 4-byte TIFF header at the
// beginning, check whether this is desired to be part of the output, or
// should be removed.
if (!WriteFile(exif_filename, exif.data(), exif.size())) {
fprintf(stderr, "Error while writing the exif file\n");
return 1;
}
printf("Successfully wrote %s\n", exif_filename);
}
return 0;
}

View File

@ -0,0 +1,250 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This C++ example decodes a JPEG XL image in one shot (all input bytes
// available at once). The example outputs the pixels and color information to a
// floating point image and an ICC profile on disk.
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <vector>
/** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are
* stored as floating point, as interleaved RGBA (4 floating point values per
* pixel), line per line from top to bottom. Pixel values have nominal range
* 0..1 but may go beyond this range for HDR or wide gamut. The ICC profile
* describes the color format of the pixel data.
*/
bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size,
std::vector<float>* pixels, size_t* xsize,
size_t* ysize, std::vector<uint8_t>* icc_profile) {
// Multi-threaded parallel runner.
auto runner = JxlResizableParallelRunnerMake(nullptr);
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS !=
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
return false;
}
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
JxlResizableParallelRunner,
runner.get())) {
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
return false;
}
JxlBasicInfo info;
JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
JxlDecoderSetInput(dec.get(), jxl, size);
JxlDecoderCloseInput(dec.get());
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
fprintf(stderr, "Decoder error\n");
return false;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
fprintf(stderr, "Error, already provided all input\n");
return false;
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
return false;
}
*xsize = info.xsize;
*ysize = info.ysize;
JxlResizableParallelRunnerSetThreads(
runner.get(),
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
} else if (status == JXL_DEC_COLOR_ENCODING) {
// Get the ICC color profile of the pixel data
size_t icc_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
&icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
return false;
}
icc_profile->resize(icc_size);
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile->data(), icc_profile->size())) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
size_t buffer_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
return false;
}
if (buffer_size != *xsize * *ysize * 16) {
fprintf(stderr, "Invalid out buffer size %" PRIu64 " %" PRIu64 "\n",
static_cast<uint64_t>(buffer_size),
static_cast<uint64_t>(*xsize * *ysize * 16));
return false;
}
pixels->resize(*xsize * *ysize * 4);
void* pixels_buffer = (void*)pixels->data();
size_t pixels_buffer_size = pixels->size() * sizeof(float);
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
pixels_buffer,
pixels_buffer_size)) {
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
return false;
}
} else if (status == JXL_DEC_FULL_IMAGE) {
// Nothing to do. Do not yet return. If the image is an animation, more
// full frames may be decoded. This example only keeps the last one.
} else if (status == JXL_DEC_SUCCESS) {
// All decoding successfully finished.
// It's not required to call JxlDecoderReleaseInput(dec.get()) here since
// the decoder will be destroyed.
return true;
} else {
fprintf(stderr, "Unknown decoder status\n");
return false;
}
}
}
/** Writes to .pfm file (Portable FloatMap). Gimp, tev viewer and ImageMagick
* support viewing this format.
* The input pixels are given as 32-bit floating point with 4-channel RGBA.
* The alpha channel will not be written since .pfm does not support it.
*/
bool WritePFM(const char* filename, const float* pixels, size_t xsize,
size_t ysize) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Could not open %s for writing", filename);
return false;
}
uint32_t endian_test = 1;
uint8_t little_endian[4];
memcpy(little_endian, &endian_test, 4);
fprintf(file, "PF\n%d %d\n%s\n", (int)xsize, (int)ysize,
little_endian[0] ? "-1.0" : "1.0");
for (int y = ysize - 1; y >= 0; y--) {
for (size_t x = 0; x < xsize; x++) {
for (size_t c = 0; c < 3; c++) {
const float* f = &pixels[(y * xsize + x) * 4 + c];
fwrite(f, 4, 1, file);
}
}
}
if (fclose(file) != 0) {
return false;
}
return true;
}
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
FILE* file = fopen(filename, "rb");
if (!file) {
return false;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return false;
}
long size = ftell(file);
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
return false;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return false;
}
out->resize(size);
size_t readsize = fread(out->data(), 1, size, file);
if (fclose(file) != 0) {
return false;
}
return readsize == static_cast<size_t>(size);
}
bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Could not open %s for writing", filename);
return false;
}
fwrite(data, 1, size, file);
if (fclose(file) != 0) {
return false;
}
return true;
}
int main(int argc, char* argv[]) {
if (argc != 4) {
fprintf(stderr,
"Usage: %s <jxl> <pfm> <icc>\n"
"Where:\n"
" jxl = input JPEG XL image filename\n"
" pfm = output Portable FloatMap image filename\n"
" icc = output ICC color profile filename\n"
"Output files will be overwritten.\n",
argv[0]);
return 1;
}
const char* jxl_filename = argv[1];
const char* pfm_filename = argv[2];
const char* icc_filename = argv[3];
std::vector<uint8_t> jxl;
if (!LoadFile(jxl_filename, &jxl)) {
fprintf(stderr, "couldn't load %s\n", jxl_filename);
return 1;
}
std::vector<float> pixels;
std::vector<uint8_t> icc_profile;
size_t xsize = 0, ysize = 0;
if (!DecodeJpegXlOneShot(jxl.data(), jxl.size(), &pixels, &xsize, &ysize,
&icc_profile)) {
fprintf(stderr, "Error while decoding the jxl file\n");
return 1;
}
if (!WritePFM(pfm_filename, pixels.data(), xsize, ysize)) {
fprintf(stderr, "Error while writing the PFM image file\n");
return 1;
}
if (!WriteFile(icc_filename, icc_profile.data(), icc_profile.size())) {
fprintf(stderr, "Error while writing the ICC profile file\n");
return 1;
}
printf("Successfully wrote %s and %s\n", pfm_filename, icc_filename);
return 0;
}

View File

@ -0,0 +1,241 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This C++ example decodes a JPEG XL image progressively (input bytes are
// passed in chunks). The example outputs the intermediate steps to PAM files.
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <vector>
bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) {
FILE* fp = fopen(filename, "wb");
if (!fp) {
fprintf(stderr, "Could not open %s for writing", filename);
return false;
}
fprintf(fp,
"P7\nWIDTH %" PRIu64 "\nHEIGHT %" PRIu64
"\nDEPTH 4\nMAXVAL 255\nTUPLTYPE "
"RGB_ALPHA\nENDHDR\n",
static_cast<uint64_t>(w), static_cast<uint64_t>(h));
size_t num_bytes = w * h * 4;
if (fwrite(buffer, 1, num_bytes, fp) != num_bytes) {
fclose(fp);
return false;
};
if (fclose(fp) != 0) {
return false;
}
return true;
}
/** Decodes JPEG XL image to 8-bit integer RGBA pixels and an ICC Profile, in a
* progressive way, saving the intermediate steps.
*/
bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size,
const char* filename, size_t chunksize) {
std::vector<uint8_t> pixels;
std::vector<uint8_t> icc_profile;
size_t xsize = 0, ysize = 0;
// Multi-threaded parallel runner.
auto runner = JxlResizableParallelRunnerMake(nullptr);
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS !=
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
return false;
}
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
JxlResizableParallelRunner,
runner.get())) {
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
return false;
}
JxlBasicInfo info;
JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
size_t seen = 0;
JxlDecoderSetInput(dec.get(), jxl, chunksize);
size_t remaining = chunksize;
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
fprintf(stderr, "Decoder error\n");
return false;
} else if (status == JXL_DEC_NEED_MORE_INPUT || status == JXL_DEC_SUCCESS ||
status == JXL_DEC_FULL_IMAGE) {
seen += remaining - JxlDecoderReleaseInput(dec.get());
printf("Flushing after %" PRIu64 " bytes\n", static_cast<uint64_t>(seen));
if (status == JXL_DEC_NEED_MORE_INPUT &&
JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec.get())) {
printf("flush error (no preview yet)\n");
} else {
char fname[1024];
if (snprintf(fname, 1024, "%s-%" PRIu64 ".pam", filename,
static_cast<uint64_t>(seen)) >= 1024) {
fprintf(stderr, "Filename too long\n");
return false;
};
if (!WritePAM(fname, pixels.data(), xsize, ysize)) {
fprintf(stderr, "Error writing progressive output\n");
}
}
remaining = size - seen;
if (remaining > chunksize) remaining = chunksize;
if (remaining == 0) {
if (status == JXL_DEC_NEED_MORE_INPUT) {
fprintf(stderr, "Error, already provided all input\n");
return false;
} else {
return true;
}
}
JxlDecoderSetInput(dec.get(), jxl + seen, remaining);
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
return false;
}
xsize = info.xsize;
ysize = info.ysize;
JxlResizableParallelRunnerSetThreads(
runner.get(),
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
} else if (status == JXL_DEC_COLOR_ENCODING) {
// Get the ICC color profile of the pixel data
size_t icc_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(
dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
return false;
}
icc_profile.resize(icc_size);
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL,
icc_profile.data(), icc_profile.size())) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
size_t buffer_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
return false;
}
if (buffer_size != xsize * ysize * 4) {
fprintf(stderr, "Invalid out buffer size %" PRIu64 " != %" PRIu64 "\n",
static_cast<uint64_t>(buffer_size),
static_cast<uint64_t>(xsize * ysize * 4));
return false;
}
pixels.resize(xsize * ysize * 4);
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
pixels.data(),
pixels.size())) {
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
return false;
}
} else {
fprintf(stderr, "Unknown decoder status\n");
return false;
}
}
}
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
FILE* file = fopen(filename, "rb");
if (!file) {
return false;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return false;
}
long size = ftell(file);
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
return false;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return false;
}
out->resize(size);
size_t readsize = fread(out->data(), 1, size, file);
if (fclose(file) != 0) {
return false;
}
return readsize == static_cast<size_t>(size);
}
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(
stderr,
"Usage: %s <jxl> <basename> [chunksize]\n"
"Where:\n"
" jxl = input JPEG XL image filename\n"
" basename = prefix of output filenames\n"
" chunksize = loads chunksize bytes at a time and writes\n"
" intermediate results to basename-[bytes loaded].pam\n"
"Output files will be overwritten.\n",
argv[0]);
return 1;
}
const char* jxl_filename = argv[1];
const char* png_filename = argv[2];
std::vector<uint8_t> jxl;
if (!LoadFile(jxl_filename, &jxl)) {
fprintf(stderr, "couldn't load %s\n", jxl_filename);
return 1;
}
size_t chunksize = jxl.size();
if (argc > 3) {
long cs = atol(argv[3]);
if (cs < 100) {
fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n");
return 1;
}
chunksize = cs;
}
if (!DecodeJpegXlProgressive(jxl.data(), jxl.size(), png_filename,
chunksize)) {
fprintf(stderr, "Error while decoding the jxl file\n");
return 1;
}
return 0;
}

View File

@ -0,0 +1,276 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This example encodes a file containing a floating point image to another
// file containing JPEG XL image with a single frame.
#include <jxl/encode.h>
#include <jxl/encode_cxx.h>
#include <jxl/thread_parallel_runner.h>
#include <jxl/thread_parallel_runner_cxx.h>
#include <limits.h>
#include <string.h>
#include <sstream>
#include <string>
#include <vector>
/**
* Reads from .pfm file (Portable FloatMap)
*
* @param filename name of the file to read
* @param pixels vector to fill with loaded pixels as 32-bit floating point with
* 3-channel RGB
* @param xsize set to width of loaded image
* @param ysize set to height of loaded image
*/
bool ReadPFM(const char* filename, std::vector<float>* pixels, uint32_t* xsize,
uint32_t* ysize) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Could not open %s for reading.\n", filename);
return false;
}
uint32_t endian_test = 1;
uint8_t little_endian[4];
memcpy(little_endian, &endian_test, 4);
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return false;
}
long size = ftell(file);
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
return false;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return false;
}
std::vector<char> data;
data.resize(size);
size_t readsize = fread(data.data(), 1, size, file);
if ((long)readsize != size) {
return false;
}
if (fclose(file) != 0) {
return false;
}
std::stringstream datastream;
std::string datastream_content(data.data(), data.size());
datastream.str(datastream_content);
std::string pf_token;
getline(datastream, pf_token, '\n');
if (pf_token != "PF") {
fprintf(stderr,
"%s doesn't seem to be a 3 channel Portable FloatMap file (missing "
"'PF\\n' "
"bytes).\n",
filename);
return false;
}
std::string xsize_token;
getline(datastream, xsize_token, ' ');
*xsize = std::stoi(xsize_token);
std::string ysize_token;
getline(datastream, ysize_token, '\n');
*ysize = std::stoi(ysize_token);
std::string endianness_token;
getline(datastream, endianness_token, '\n');
bool input_little_endian;
if (endianness_token == "1.0") {
input_little_endian = false;
} else if (endianness_token == "-1.0") {
input_little_endian = true;
} else {
fprintf(stderr,
"%s doesn't seem to be a Portable FloatMap file (endianness token "
"isn't '1.0' or '-1.0').\n",
filename);
return false;
}
size_t offset = pf_token.size() + 1 + xsize_token.size() + 1 +
ysize_token.size() + 1 + endianness_token.size() + 1;
if (data.size() != *ysize * *xsize * 3 * 4 + offset) {
fprintf(stderr,
"%s doesn't seem to be a Portable FloatMap file (pixel data bytes "
"are %d, but expected %d * %d * 3 * 4 + %d (%d).\n",
filename, (int)data.size(), (int)*ysize, (int)*xsize, (int)offset,
(int)(*ysize * *xsize * 3 * 4 + offset));
return false;
}
if (!!little_endian[0] != input_little_endian) {
fprintf(stderr,
"%s has a different endianness than we do, conversion is not "
"supported.\n",
filename);
return false;
}
pixels->resize(*ysize * *xsize * 3);
for (int y = *ysize - 1; y >= 0; y--) {
for (int x = 0; x < (int)*xsize; x++) {
for (int c = 0; c < 3; c++) {
memcpy(pixels->data() + (y * *xsize + x) * 3 + c, data.data() + offset,
sizeof(float));
offset += sizeof(float);
}
}
}
return true;
}
/**
* Compresses the provided pixels.
*
* @param pixels input pixels
* @param xsize width of the input image
* @param ysize height of the input image
* @param compressed will be populated with the compressed bytes
*/
bool EncodeJxlOneshot(const std::vector<float>& pixels, const uint32_t xsize,
const uint32_t ysize, std::vector<uint8_t>* compressed) {
auto enc = JxlEncoderMake(/*memory_manager=*/nullptr);
auto runner = JxlThreadParallelRunnerMake(
/*memory_manager=*/nullptr,
JxlThreadParallelRunnerDefaultNumWorkerThreads());
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(),
JxlThreadParallelRunner,
runner.get())) {
fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
return false;
}
JxlPixelFormat pixel_format = {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
JxlBasicInfo basic_info;
JxlEncoderInitBasicInfo(&basic_info);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.bits_per_sample = 32;
basic_info.exponent_bits_per_sample = 8;
basic_info.uses_original_profile = JXL_FALSE;
if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) {
fprintf(stderr, "JxlEncoderSetBasicInfo failed\n");
return false;
}
JxlColorEncoding color_encoding = {};
JxlColorEncodingSetToSRGB(&color_encoding,
/*is_gray=*/pixel_format.num_channels < 3);
if (JXL_ENC_SUCCESS !=
JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) {
fprintf(stderr, "JxlEncoderSetColorEncoding failed\n");
return false;
}
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
if (JXL_ENC_SUCCESS !=
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
(void*)pixels.data(),
sizeof(float) * pixels.size())) {
fprintf(stderr, "JxlEncoderAddImageFrame failed\n");
return false;
}
JxlEncoderCloseInput(enc.get());
compressed->resize(64);
uint8_t* next_out = compressed->data();
size_t avail_out = compressed->size() - (next_out - compressed->data());
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
size_t offset = next_out - compressed->data();
compressed->resize(compressed->size() * 2);
next_out = compressed->data() + offset;
avail_out = compressed->size() - offset;
}
}
compressed->resize(next_out - compressed->data());
if (JXL_ENC_SUCCESS != process_result) {
fprintf(stderr, "JxlEncoderProcessOutput failed\n");
return false;
}
return true;
}
/**
* Writes bytes to file.
*/
bool WriteFile(const std::vector<uint8_t>& bytes, const char* filename) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Could not open %s for writing\n", filename);
return false;
}
if (fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), file) !=
bytes.size()) {
fprintf(stderr, "Could not write bytes to %s\n", filename);
fclose(file);
return false;
}
if (fclose(file) != 0) {
fprintf(stderr, "Could not close %s\n", filename);
return false;
}
return true;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
fprintf(stderr,
"Usage: %s <pfm> <jxl>\n"
"Where:\n"
" pfm = input Portable FloatMap image filename\n"
" jxl = output JPEG XL image filename\n"
"Output files will be overwritten.\n",
argv[0]);
return 1;
}
const char* pfm_filename = argv[1];
const char* jxl_filename = argv[2];
std::vector<float> pixels;
uint32_t xsize;
uint32_t ysize;
if (!ReadPFM(pfm_filename, &pixels, &xsize, &ysize)) {
fprintf(stderr, "Couldn't load %s\n", pfm_filename);
return 2;
}
std::vector<uint8_t> compressed;
if (!EncodeJxlOneshot(pixels, xsize, ysize, &compressed)) {
fprintf(stderr, "Couldn't encode jxl\n");
return 3;
}
if (!WriteFile(compressed, jxl_filename)) {
fprintf(stderr, "Couldn't write jxl file\n");
return 4;
}
return 0;
}

View File

@ -0,0 +1,11 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
add_executable(decode_oneshot ${CMAKE_CURRENT_LIST_DIR}/decode_oneshot.cc)
target_link_libraries(decode_oneshot jxl_dec jxl_threads)
add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.cc)
target_link_libraries(decode_progressive jxl_dec jxl_threads)
add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc)
target_link_libraries(encode_oneshot jxl jxl_threads)

298
third-party/libjxl/libjxl/lib/BUILD vendored Normal file
View File

@ -0,0 +1,298 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Load sources/headers/tests lists.
load(
"jxl_lists.bzl",
"libjxl_base_sources",
"libjxl_codec_apng_sources",
"libjxl_codec_exr_sources",
"libjxl_codec_gif_sources",
"libjxl_codec_jpegli_sources",
"libjxl_codec_jpg_sources",
"libjxl_codec_jxl_sources",
"libjxl_codec_npy_sources",
"libjxl_codec_pgx_sources",
"libjxl_codec_pnm_sources",
"libjxl_dec_box_sources",
"libjxl_dec_jpeg_sources",
"libjxl_dec_sources",
"libjxl_enc_sources",
"libjxl_extras_for_tools_sources",
"libjxl_extras_sources",
#'libjxl_gbench_sources',
"libjxl_jpegli_lib_version",
"libjxl_jpegli_libjpeg_helper_files",
"libjxl_jpegli_sources",
"libjxl_jpegli_testlib_files",
"libjxl_jpegli_tests",
"libjxl_major_version",
"libjxl_minor_version",
"libjxl_patch_version",
"libjxl_public_headers",
"libjxl_testlib_files",
"libjxl_tests",
"libjxl_threads_public_headers",
"libjxl_threads_sources",
)
load(
"jxl_vars.bzl",
"libjxl_deps_brotli",
"libjxl_deps_exr",
"libjxl_deps_gif",
"libjxl_deps_gtest",
"libjxl_deps_hwy",
"libjxl_deps_hwy_nanobenchmark",
"libjxl_deps_hwy_test_util",
"libjxl_deps_jpeg",
"libjxl_deps_jxl_box",
"libjxl_deps_png",
"libjxl_deps_runfiles",
"libjxl_deps_skcms",
"libjxl_deps_testdata",
"libjxl_root_package",
"libjxl_test_shards",
"libjxl_test_timeouts",
)
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
DEFAULT_VISIBILITY = ["//:__subpackages__"]
DEFAULT_COMPATIBILITY = []
INCLUDES_DIR = "include"
package(
default_visibility = ["//:__subpackages__"],
)
licenses(["notice"])
exports_files(["LICENSE"])
EXPORT_TEMPLATE = """
#ifndef @_EXPORT_H
#define @_EXPORT_H
#define @_EXPORT
#define @_NO_EXPORT
#ifndef @_DEPRECATED
# define @_DEPRECATED __attribute__ ((__deprecated__))
#endif
#endif
"""
JXL_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_export.h"
genrule(
name = "create_jxl_export",
outs = [JXL_EXPORT_H],
cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL") + "' > $@",
compatible_with = DEFAULT_COMPATIBILITY,
)
JXL_THREADS_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_threads_export.h"
genrule(
name = "create_jxl_threads_export",
outs = [JXL_THREADS_EXPORT_H],
cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL_THREADS") + "' > $@",
compatible_with = DEFAULT_COMPATIBILITY,
)
JXL_VERSION_H = INCLUDES_DIR + "/jxl/version.h"
expand_template(
name = "expand_jxl_version",
out = JXL_VERSION_H,
compatible_with = DEFAULT_COMPATIBILITY,
substitutions = {
"@JPEGXL_MAJOR_VERSION@": str(libjxl_major_version),
"@JPEGXL_MINOR_VERSION@": str(libjxl_minor_version),
"@JPEGXL_PATCH_VERSION@": str(libjxl_patch_version),
},
template = "jxl/version.h.in",
)
cc_library(
name = "jxl_version",
hdrs = [JXL_VERSION_H],
compatible_with = DEFAULT_COMPATIBILITY,
strip_include_prefix = INCLUDES_DIR,
)
JPEGLI_JCONFIG_H = INCLUDES_DIR + "/jpegli/jconfig.h"
JPEGLI_JMORECFG_H = INCLUDES_DIR + "/jpegli/jmorecfg.h"
JPEGLI_JPEGLIB_H = INCLUDES_DIR + "/jpegli/jpeglib.h"
copy_file(
name = "expand_jconfig",
src = "@libjpeg_turbo//:jconfig.h",
out = JPEGLI_JCONFIG_H,
compatible_with = DEFAULT_COMPATIBILITY,
)
copy_file(
name = "copy_jmorecfg",
src = "@libjpeg_turbo//:jmorecfg.h",
out = JPEGLI_JMORECFG_H,
compatible_with = DEFAULT_COMPATIBILITY,
)
copy_file(
name = "copy_jpeglib",
src = "@libjpeg_turbo//:jpeglib.h",
out = JPEGLI_JPEGLIB_H,
compatible_with = DEFAULT_COMPATIBILITY,
)
cc_library(
name = "includes",
hdrs = libjxl_public_headers + [JXL_EXPORT_H],
compatible_with = DEFAULT_COMPATIBILITY,
strip_include_prefix = INCLUDES_DIR,
deps = [":jxl_version"],
)
cc_library(
name = "libjpeg_includes",
hdrs = [
JPEGLI_JCONFIG_H,
JPEGLI_JMORECFG_H,
JPEGLI_JPEGLIB_H,
],
compatible_with = DEFAULT_COMPATIBILITY,
strip_include_prefix = INCLUDES_DIR + "/jpegli",
)
cc_library(
name = "base",
srcs = [path for path in libjxl_base_sources if path.endswith(".cc")],
hdrs = [path for path in libjxl_base_sources if path.endswith(".h")],
compatible_with = DEFAULT_COMPATIBILITY,
deps = [
":includes",
] + libjxl_deps_hwy,
)
cc_library(
name = "jpegxl",
srcs = libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources,
compatible_with = DEFAULT_COMPATIBILITY,
defines = ["JPEGXL_ENABLE_SKCMS=1"],
deps = [
":base",
":includes",
] + libjxl_deps_brotli + libjxl_deps_hwy + libjxl_deps_skcms,
)
cc_library(
name = "jpegxl_private",
hdrs = [
path
for path in libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources
if path.endswith(".h") and not path.endswith("-inl.h")
],
compatible_with = DEFAULT_COMPATIBILITY,
deps = [":jpegxl"],
)
cc_library(
name = "jpegxl_threads",
srcs = libjxl_threads_sources,
hdrs = libjxl_threads_public_headers + [JXL_THREADS_EXPORT_H],
compatible_with = DEFAULT_COMPATIBILITY,
strip_include_prefix = INCLUDES_DIR,
deps = [
":base",
":includes",
],
)
CODEC_FILES = libjxl_codec_apng_sources + libjxl_codec_exr_sources + libjxl_codec_gif_sources + libjxl_codec_jpegli_sources + libjxl_codec_jpg_sources + libjxl_codec_jxl_sources + libjxl_codec_npy_sources + libjxl_codec_pgx_sources + libjxl_codec_pnm_sources
CODEC_SRCS = [path for path in CODEC_FILES if path.endswith(".cc")]
CODEC_HDRS = [path for path in CODEC_FILES if path.endswith(".h")]
cc_library(
name = "jpegli",
srcs = libjxl_jpegli_sources,
hdrs = [
"jpegli/common_internal.h", # TODO(eustas): should not be here
],
compatible_with = DEFAULT_COMPATIBILITY,
deps = [
":jpegxl_private",
":libjpeg_includes",
] + libjxl_deps_hwy,
)
# TODO(eustas): build codecs separately?
cc_library(
name = "jpegxl_extras",
srcs = libjxl_extras_sources + libjxl_extras_for_tools_sources + CODEC_SRCS,
hdrs = CODEC_HDRS,
compatible_with = DEFAULT_COMPATIBILITY,
defines = [
"JPEGXL_ENABLE_APNG=1",
"JPEGXL_ENABLE_EXR=1",
"JPEGXL_ENABLE_GIF=1",
"JPEGXL_ENABLE_JPEG=1",
"JPEGXL_ENABLE_JPEGLI=1",
],
deps = [
":jpegli",
":jpegxl_private",
":jpegxl_threads",
":jxl_version",
] + libjxl_deps_exr + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png,
)
TESTLIB_FILES = libjxl_testlib_files + libjxl_jpegli_testlib_files + libjxl_jpegli_libjpeg_helper_files
cc_library(
name = "test_utils",
testonly = 1,
srcs = [path for path in TESTLIB_FILES if not path.endswith(".h")],
hdrs = [path for path in TESTLIB_FILES if path.endswith(".h")],
compatible_with = DEFAULT_COMPATIBILITY,
defines = [
'JPEGXL_ROOT_PACKAGE=\'"' + libjxl_root_package + '"\'',
],
deps = [
":jpegli",
":jpegxl_extras",
":jpegxl_private",
] + libjxl_deps_runfiles,
)
TESTS = [path.partition(".")[0] for path in libjxl_tests + libjxl_jpegli_tests]
[
cc_test(
name = test,
timeout = libjxl_test_timeouts.get(test, "moderate"),
srcs = [
test + ".cc",
"jpegli/testing.h",
"jxl/testing.h",
],
data = ["//:testdata"],
shard_count = libjxl_test_shards.get(test, 1),
deps = [
":jpegxl_extras",
":jpegxl_private",
":jpegxl_threads",
":test_utils",
] + libjxl_deps_gtest + libjxl_deps_hwy_test_util + libjxl_deps_hwy_nanobenchmark + libjxl_deps_jxl_box,
)
for test in TESTS
]

View File

@ -0,0 +1,167 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
set(JPEGXL_MAJOR_VERSION 0)
set(JPEGXL_MINOR_VERSION 9)
set(JPEGXL_PATCH_VERSION 0)
set(JPEGXL_LIBRARY_VERSION
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}")
# This is the library API/ABI compatibility version. Changing this value makes
# the shared library incompatible with previous version. A program linked
# against this shared library SOVERSION will not run with an older SOVERSION.
# It is important to update this value when making incompatible API/ABI changes
# so that programs that depend on libjxl can update their dependencies. Semantic
# versioning allows 0.y.z to have incompatible changes in minor versions.
set(JPEGXL_SO_MINOR_VERSION 9)
if (JPEGXL_MAJOR_VERSION EQUAL 0)
set(JPEGXL_LIBRARY_SOVERSION
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}")
else()
set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}")
endif()
# List of warning and feature flags for our library and tests.
if (MSVC)
set(JPEGXL_INTERNAL_FLAGS
# TODO(janwas): add flags
)
else ()
set(JPEGXL_INTERNAL_FLAGS
# F_FLAGS
-fmerge-all-constants
-fno-builtin-fwrite
-fno-builtin-fread
# WARN_FLAGS
-Wall
-Wextra
-Wc++11-compat
-Warray-bounds
-Wformat-security
-Wimplicit-fallthrough
-Wno-register # Needed by public headers in lcms
-Wno-unused-function
-Wno-unused-parameter
-Wnon-virtual-dtor
-Woverloaded-virtual
-Wvla
)
# Warning flags supported by clang.
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wdeprecated-increment-bool
# TODO(deymo): Add -Wextra-semi once we update third_party/highway.
# -Wextra-semi
-Wfloat-overflow-conversion
-Wfloat-zero-conversion
-Wfor-loop-analysis
-Wgnu-redeclared-enum
-Winfinite-recursion
-Wliteral-conversion
-Wno-c++98-compat
-Wno-unused-command-line-argument
-Wprivate-header
-Wself-assign
-Wstring-conversion
-Wtautological-overlap-compare
-Wthread-safety-analysis
-Wundefined-func-template
-Wunreachable-code
-Wunused-comparison
)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0)
list(APPEND HWY_FLAGS -Wc++2a-extensions)
endif()
endif() # Clang
if (WIN32)
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wno-cast-align
-Wno-double-promotion
-Wno-float-equal
-Wno-format-nonliteral
-Wno-shadow
-Wno-sign-conversion
-Wno-zero-as-null-pointer-constant
)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wno-used-but-marked-unused
-Wno-unused-template
-Wno-unused-member-function
-Wno-shadow-field-in-constructor
-Wno-language-extension-token
-Wno-global-constructors
-Wno-c++98-compat-pedantic
)
endif() # Clang
else() # WIN32
list(APPEND JPEGXL_INTERNAL_FLAGS
-fsized-deallocation
-fno-exceptions
# Language flags
-fmath-errno
)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-fnew-alignment=8
-fno-cxx-exceptions
-fno-slp-vectorize
-fno-vectorize
-disable-free
-disable-llvm-verifier
)
endif() # Clang
endif() # WIN32
endif() #!MSVC
# strips the -static suffix from all the elements in LIST
function(strip_static OUTPUT_VAR LIB_LIST)
foreach(lib IN LISTS ${LIB_LIST})
string(REGEX REPLACE "-static$" "" lib "${lib}")
list(APPEND out_list "${lib}")
endforeach()
set(${OUTPUT_VAR} ${out_list} PARENT_SCOPE)
endfunction()
# The jxl library definition.
include(jxl.cmake)
# Other libraries outside the core jxl library.
if(JPEGXL_ENABLE_TOOLS)
include(jxl_extras.cmake)
endif()
include(jxl_threads.cmake)
if (JPEGXL_ENABLE_JPEGLI)
include(jpegli.cmake)
endif()
# Install all the library headers from the source and the generated ones. There
# is no distinction on which libraries use which header since it is expected
# that all developer libraries are available together at build time.
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/jxl
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/jxl
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
if(BUILD_TESTING)
cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198
include(GoogleTest)
endif()
# Tests for the jxl library.
include(jxl_tests.cmake)
if(BUILD_TESTING)
# Google benchmark for the jxl library
include(jxl_benchmark.cmake)
endif()

View File

@ -0,0 +1,30 @@
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
function(jxl_discover_tests TESTNAME)
if (CMAKE_VERSION VERSION_LESS "3.10.3")
gtest_discover_tests(${TESTNAME} TIMEOUT 240)
else ()
gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 240)
endif ()
endfunction()
function(jxl_link_libraries DST SRC)
if (CMAKE_VERSION VERSION_LESS "3.13.5")
target_include_directories(${DST} SYSTEM PUBLIC
$<BUILD_INTERFACE:$<TARGET_PROPERTY:${SRC},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>>
)
add_dependencies(${DST} ${SRC})
else()
target_link_libraries(${DST} PUBLIC ${SRC})
endif()
endfunction()
if (CMAKE_VERSION VERSION_LESS "3.12.4")
set(JXL_HWY_INCLUDE_DIRS "$<BUILD_INTERFACE:$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>>")
else()
set(JXL_HWY_INCLUDE_DIRS "$<BUILD_INTERFACE:$<TARGET_PROPERTY:$<IF:$<TARGET_EXISTS:hwy::hwy>,hwy::hwy,hwy>,INTERFACE_INCLUDE_DIRECTORIES>>")
endif()

View File

@ -0,0 +1,27 @@
APNG Disassembler 2.8
Deconstructs APNG files into individual frames.
http://apngdis.sourceforge.net
Copyright (c) 2010-2015 Max Stepin
maxst at users.sourceforge.net
zlib license
------------
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@ -0,0 +1,5 @@
## JPEG XL "extras"
The files in this directory do not form part of the library or codec and are
only used by tests or specific internal tools that have access to the internals
of the library.

View File

@ -0,0 +1,173 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/codec.h"
#include <jxl/decode.h>
#include <jxl/types.h>
#include "lib/extras/dec/decode.h"
#include "lib/extras/enc/apng.h"
#include "lib/extras/enc/exr.h"
#include "lib/extras/enc/jpg.h"
#include "lib/extras/enc/pgx.h"
#include "lib/extras/enc/pnm.h"
#include "lib/extras/packed_image.h"
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/image_bundle.h"
namespace jxl {
namespace {
// Any valid encoding is larger (ensures codecs can read the first few bytes)
constexpr size_t kMinBytes = 9;
} // namespace
Status SetFromBytes(const Span<const uint8_t> bytes,
const extras::ColorHints& color_hints, CodecInOut* io,
ThreadPool* pool, const SizeConstraints* constraints,
extras::Codec* orig_codec) {
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
extras::PackedPixelFile ppf;
if (extras::DecodeBytes(bytes, color_hints, &ppf, constraints, orig_codec)) {
return ConvertPackedPixelFileToCodecInOut(ppf, pool, io);
}
return JXL_FAILURE("Codecs failed to decode");
}
Status Encode(const CodecInOut& io, const extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
std::vector<uint8_t>* bytes, ThreadPool* pool) {
bytes->clear();
JXL_CHECK(!io.Main().c_current().ICC().empty());
JXL_CHECK(!c_desired.ICC().empty());
io.CheckMetadata();
if (io.Main().IsJPEG()) {
JXL_WARNING("Writing JPEG data as pixels");
}
JxlPixelFormat format = {
0, // num_channels is ignored by the converter
bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
0};
const bool floating_point = bits_per_sample > 16;
std::unique_ptr<extras::Encoder> encoder;
std::ostringstream os;
switch (codec) {
case extras::Codec::kPNG:
encoder = extras::GetAPNGEncoder();
if (encoder) {
break;
} else {
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
}
case extras::Codec::kJPG:
format.data_type = JXL_TYPE_UINT8;
encoder = extras::GetJPEGEncoder();
if (encoder) {
os << io.jpeg_quality;
encoder->SetOption("q", os.str());
break;
} else {
return JXL_FAILURE("JPEG XL was built without JPEG support");
}
case extras::Codec::kPNM:
if (io.Main().HasAlpha()) {
encoder = extras::GetPAMEncoder();
} else if (io.Main().IsGray()) {
encoder = extras::GetPGMEncoder();
} else if (!floating_point) {
encoder = extras::GetPPMEncoder();
} else {
format.data_type = JXL_TYPE_FLOAT;
format.endianness = JXL_LITTLE_ENDIAN;
encoder = extras::GetPFMEncoder();
}
break;
case extras::Codec::kPGX:
encoder = extras::GetPGXEncoder();
break;
case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kEXR:
format.data_type = JXL_TYPE_FLOAT;
encoder = extras::GetEXREncoder();
if (encoder) {
break;
} else {
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
}
case extras::Codec::kJXL:
return JXL_FAILURE("TODO: encode using Codec::kJXL");
case extras::Codec::kUnknown:
return JXL_FAILURE("Cannot encode using Codec::kUnknown");
}
if (!encoder) {
return JXL_FAILURE("Invalid codec.");
}
extras::PackedPixelFile ppf;
JXL_RETURN_IF_ERROR(
ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
ppf.info.bits_per_sample = bits_per_sample;
if (format.data_type == JXL_TYPE_FLOAT) {
ppf.info.bits_per_sample = 32;
ppf.info.exponent_bits_per_sample = 8;
}
extras::EncodedImage encoded_image;
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool));
JXL_ASSERT(encoded_image.bitstreams.size() == 1);
*bytes = encoded_image.bitstreams[0];
return true;
}
Status Encode(const CodecInOut& io, const ColorEncoding& c_desired,
size_t bits_per_sample, const std::string& pathname,
std::vector<uint8_t>* bytes, ThreadPool* pool) {
std::string extension;
const extras::Codec codec = extras::CodecFromPath(
pathname, &bits_per_sample, /* basename */ nullptr, &extension);
// Warn about incorrect usage of PGM/PGX/PPM - only the latter supports
// color, but CodecFromPath lumps them all together.
if (codec == extras::Codec::kPNM && extension != ".pfm") {
if (io.Main().HasAlpha() && extension != ".pam") {
JXL_WARNING(
"For images with alpha, the filename should end with .pam.\n");
} else if (!io.Main().IsGray() && extension == ".pgm") {
JXL_WARNING("For color images, the filename should end with .ppm.\n");
} else if (io.Main().IsGray() && extension == ".ppm") {
JXL_WARNING(
"For grayscale images, the filename should not end with .ppm.\n");
}
if (bits_per_sample > 16) {
JXL_WARNING("PPM only supports up to 16 bits per sample");
bits_per_sample = 16;
}
} else if (codec == extras::Codec::kPGX && !io.Main().IsGray()) {
JXL_WARNING("Storing color image to PGX - use .ppm extension instead.\n");
}
if (bits_per_sample > 16 && codec == extras::Codec::kPNG) {
JXL_WARNING("PNG only supports up to 16 bits per sample");
bits_per_sample = 16;
}
return Encode(io, codec, c_desired, bits_per_sample, bytes, pool);
}
Status Encode(const CodecInOut& io, const std::string& pathname,
std::vector<uint8_t>* bytes, ThreadPool* pool) {
// TODO(lode): need to take the floating_point_sample field into account
return Encode(io, io.metadata.m.color_encoding,
io.metadata.m.bit_depth.bits_per_sample, pathname, bytes, pool);
}
} // namespace jxl

View File

@ -0,0 +1,63 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_CODEC_H_
#define LIB_EXTRAS_CODEC_H_
// Facade for image encoders/decoders (PNG, PNM, ...).
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/dec/decode.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/field_encodings.h" // MakeBit
namespace jxl {
struct SizeConstraints;
// Decodes "bytes" and sets io->metadata.m.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status SetFromBytes(Span<const uint8_t> bytes,
const extras::ColorHints& color_hints, CodecInOut* io,
ThreadPool* pool = nullptr,
const SizeConstraints* constraints = nullptr,
extras::Codec* orig_codec = nullptr);
// Helper function to use no color_space_hint.
JXL_INLINE Status SetFromBytes(const Span<const uint8_t> bytes, CodecInOut* io,
ThreadPool* pool = nullptr,
const SizeConstraints* constraints = nullptr,
extras::Codec* orig_codec = nullptr) {
return SetFromBytes(bytes, extras::ColorHints(), io, pool, constraints,
orig_codec);
}
// Replaces "bytes" with an encoding of pixels transformed from c_current
// color space to c_desired.
Status Encode(const CodecInOut& io, extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
// Deduces codec, calls Encode and writes to file.
Status Encode(const CodecInOut& io, const ColorEncoding& c_desired,
size_t bits_per_sample, const std::string& pathname,
std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
// Same, but defaults to metadata.original color_encoding and bits_per_sample.
Status Encode(const CodecInOut& io, const std::string& pathname,
std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
} // namespace jxl
#endif // LIB_EXTRAS_CODEC_H_

View File

@ -0,0 +1,450 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/codec.h"
#include <stddef.h>
#include <stdio.h>
#include <algorithm>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "lib/extras/dec/decode.h"
#include "lib/extras/dec/pnm.h"
#include "lib/extras/enc/encode.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"
namespace jxl {
using test::ThreadPoolForTests;
namespace extras {
namespace {
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::SizeIs;
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const bool has_alpha,
const size_t bits_per_sample) {
switch (codec) {
case Codec::kJPG:
return ".jpg";
case Codec::kPGX:
return ".pgx";
case Codec::kPNG:
return ".png";
case Codec::kPNM:
if (bits_per_sample == 32) return ".pfm";
if (has_alpha) return ".pam";
return is_gray ? ".pgm" : ".ppm";
case Codec::kEXR:
return ".exr";
default:
return std::string();
}
}
void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
const PackedImage& im1, size_t bits_per_sample1,
bool lossless = true) {
ASSERT_EQ(im0.xsize, im1.xsize);
ASSERT_EQ(im0.ysize, im1.ysize);
ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
};
double factor0 = get_factor(im0.format, bits_per_sample0);
double factor1 = get_factor(im1.format, bits_per_sample1);
auto pixels0 = static_cast<const uint8_t*>(im0.pixels());
auto pixels1 = static_cast<const uint8_t*>(im1.pixels());
auto rgba0 =
test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
auto rgba1 =
test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
double tolerance =
lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
tolerance = 0.5 * std::max(factor0, factor1);
}
for (size_t y = 0; y < im0.ysize; ++y) {
for (size_t x = 0; x < im0.xsize; ++x) {
for (size_t c = 0; c < im0.format.num_channels; ++c) {
size_t ix = (y * im0.xsize + x) * 4 + c;
double val0 = rgba0[ix];
double val1 = rgba1[ix];
ASSERT_NEAR(val1, val0, tolerance)
<< "y = " << y << " x = " << x << " c = " << c;
}
}
}
}
JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
JxlColorEncoding c;
c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
c.white_point = JXL_WHITE_POINT_D65;
c.primaries = JXL_PRIMARIES_P3;
c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
// Roundtrip through internal color encoding to fill in primaries and white
// point CIE xy coordinates.
ColorEncoding c_internal;
JXL_CHECK(ConvertExternalToInternalColorEncoding(c, &c_internal));
ConvertInternalToExternalColorEncoding(c_internal, &c);
return c;
}
std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
ColorEncoding c;
JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c));
JXL_CHECK(c.CreateICC());
PaddedBytes icc = c.ICC();
return std::vector<uint8_t>(icc.begin(), icc.end());
}
void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
size_t bits_per_sample) {
uint64_t max_val = (1ull << bits_per_sample) - 1;
if (format.data_type == JXL_TYPE_UINT8) {
*out = rng->UniformU(0, max_val);
} else if (format.data_type == JXL_TYPE_UINT16) {
uint32_t val = rng->UniformU(0, max_val);
if (format.endianness == JXL_BIG_ENDIAN) {
StoreBE16(val, out);
} else {
StoreLE16(val, out);
}
} else {
ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
float val = rng->UniformF(0.0, 1.0);
uint32_t uval;
memcpy(&uval, &val, 4);
if (format.endianness == JXL_BIG_ENDIAN) {
StoreBE32(uval, out);
} else {
StoreLE32(uval, out);
}
}
}
void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
JxlPixelFormat format = image->format;
size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
uint8_t* out = static_cast<uint8_t*>(image->pixels());
size_t stride = image->xsize * format.num_channels * bytes_per_channel;
ASSERT_EQ(image->pixels_size, image->ysize * stride);
Rng rng(129);
for (size_t y = 0; y < image->ysize; ++y) {
for (size_t x = 0; x < image->xsize; ++x) {
for (size_t c = 0; c < format.num_channels; ++c) {
StoreRandomValue(out, &rng, format, bits_per_sample);
out += bytes_per_channel;
}
}
}
}
struct TestImageParams {
Codec codec;
size_t xsize;
size_t ysize;
size_t bits_per_sample;
bool is_gray;
bool add_alpha;
bool big_endian;
bool add_extra_channels;
bool ShouldTestRoundtrip() const {
if (codec == Codec::kPNG) {
return bits_per_sample <= 16;
} else if (codec == Codec::kPNM) {
// TODO(szabadka) Make PNM encoder endianness-aware.
return ((bits_per_sample <= 16 && big_endian) ||
(bits_per_sample == 32 && !add_alpha && !big_endian));
} else if (codec == Codec::kPGX) {
return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
!add_alpha);
} else if (codec == Codec::kEXR) {
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
// OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
return false;
#else
return bits_per_sample == 32 && !is_gray;
#endif
} else if (codec == Codec::kJPG) {
return bits_per_sample == 8 && !add_alpha;
} else {
return false;
}
}
JxlPixelFormat PixelFormat() const {
JxlPixelFormat format;
format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
: bits_per_sample > 8 ? JXL_TYPE_UINT16
: JXL_TYPE_UINT8);
format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
format.align = 0;
return format;
}
std::string DebugString() const {
std::ostringstream os;
os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
<< " be: " << big_endian << " ec: " << add_extra_channels;
return os.str();
}
};
void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
ppf->info.xsize = params.xsize;
ppf->info.ysize = params.ysize;
ppf->info.bits_per_sample = params.bits_per_sample;
ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
ppf->info.num_color_channels = params.is_gray ? 1 : 3;
ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
ppf->info.alpha_premultiplied = (params.codec == Codec::kEXR);
JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
ppf->icc = GenerateICC(color_encoding);
ppf->color_encoding = color_encoding;
PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
FillPackedImage(params.bits_per_sample, &frame.color);
if (params.add_extra_channels) {
for (size_t i = 0; i < 7; ++i) {
JxlPixelFormat ec_format = params.PixelFormat();
ec_format.num_channels = 1;
PackedImage ec(params.xsize, params.ysize, ec_format);
FillPackedImage(params.bits_per_sample, &ec);
frame.extra_channels.emplace_back(std::move(ec));
PackedExtraChannel pec;
pec.ec_info.bits_per_sample = params.bits_per_sample;
pec.ec_info.type = static_cast<JxlExtraChannelType>(i);
ppf->extra_channels_info.emplace_back(std::move(pec));
}
}
ppf->frames.emplace_back(std::move(frame));
}
// Ensures reading a newly written file leads to the same image pixels.
void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
if (!params.ShouldTestRoundtrip()) return;
std::string extension = ExtensionFromCodec(
params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
PackedPixelFile ppf_in;
CreateTestImage(params, &ppf_in);
EncodedImage encoded;
auto encoder = Encoder::FromExtension(extension);
if (!encoder) {
fprintf(stderr, "Skipping test because of missing codec support.\n");
return;
}
ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
ASSERT_EQ(encoded.bitstreams.size(), 1);
PackedPixelFile ppf_out;
ColorHints color_hints;
if (params.codec == Codec::kPNM || params.codec == Codec::kPGX) {
color_hints.Add("color_space",
params.is_gray ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
}
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
color_hints, &ppf_out));
if (params.codec == Codec::kPNG && ppf_out.icc.empty()) {
// Decoding a PNG may drop the ICC profile if there's a valid cICP chunk.
// Rendering intent is not preserved in this case.
EXPECT_EQ(ppf_in.color_encoding.color_space,
ppf_out.color_encoding.color_space);
EXPECT_EQ(ppf_in.color_encoding.white_point,
ppf_out.color_encoding.white_point);
if (ppf_in.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) {
EXPECT_EQ(ppf_in.color_encoding.primaries,
ppf_out.color_encoding.primaries);
}
EXPECT_EQ(ppf_in.color_encoding.transfer_function,
ppf_out.color_encoding.transfer_function);
EXPECT_EQ(ppf_out.color_encoding.rendering_intent,
JXL_RENDERING_INTENT_RELATIVE);
} else if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
params.codec != Codec::kEXR) {
EXPECT_EQ(ppf_in.icc, ppf_out.icc);
}
ASSERT_EQ(ppf_out.frames.size(), 1);
const auto& frame_in = ppf_in.frames[0];
const auto& frame_out = ppf_out.frames[0];
VerifySameImage(frame_in.color, ppf_in.info.bits_per_sample, frame_out.color,
ppf_out.info.bits_per_sample,
/*lossless=*/params.codec != Codec::kJPG);
ASSERT_EQ(frame_in.extra_channels.size(), frame_out.extra_channels.size());
ASSERT_EQ(ppf_out.extra_channels_info.size(),
frame_out.extra_channels.size());
for (size_t i = 0; i < frame_in.extra_channels.size(); ++i) {
VerifySameImage(frame_in.extra_channels[i], ppf_in.info.bits_per_sample,
frame_out.extra_channels[i], ppf_out.info.bits_per_sample,
/*lossless=*/true);
EXPECT_EQ(ppf_out.extra_channels_info[i].ec_info.type,
ppf_in.extra_channels_info[i].ec_info.type);
}
}
TEST(CodecTest, TestRoundTrip) {
ThreadPoolForTests pool(12);
TestImageParams params;
params.xsize = 7;
params.ysize = 4;
for (Codec codec :
{Codec::kPNG, Codec::kPNM, Codec::kPGX, Codec::kEXR, Codec::kJPG}) {
for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
for (bool is_gray : {false, true}) {
for (bool add_alpha : {false, true}) {
for (bool big_endian : {false, true}) {
params.codec = codec;
params.bits_per_sample = static_cast<size_t>(bits_per_sample);
params.is_gray = is_gray;
params.add_alpha = add_alpha;
params.big_endian = big_endian;
params.add_extra_channels = false;
TestRoundTrip(params, &pool);
if (codec == Codec::kPNM && add_alpha) {
params.add_extra_channels = true;
TestRoundTrip(params, &pool);
}
}
}
}
}
}
}
TEST(CodecTest, LosslessPNMRoundtrip) {
ThreadPoolForTests pool(12);
static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"};
static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"};
for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) {
for (size_t channels = 1; channels <= 4; ++channels) {
if (bit_depth == 1 && (channels == 2 || channels == 4)) continue;
std::string extension(kExtension[channels]);
std::string filename = "jxl/flower/flower_small." +
std::string(kChannels[channels]) + ".depth" +
std::to_string(bit_depth) + extension;
const PaddedBytes orig = jxl::test::ReadTestData(filename);
PackedPixelFile ppf;
ColorHints color_hints;
color_hints.Add("color_space",
channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(orig.data(), orig.size()),
color_hints, &ppf));
EncodedImage encoded;
auto encoder = Encoder::FromExtension(extension);
ASSERT_TRUE(encoder.get());
ASSERT_TRUE(encoder->Encode(ppf, &encoded, &pool));
ASSERT_EQ(encoded.bitstreams.size(), 1);
ASSERT_EQ(orig.size(), encoded.bitstreams[0].size());
EXPECT_EQ(0,
memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size()));
}
}
}
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
TEST(CodecTest, FormatNegotiation) {
const std::vector<JxlPixelFormat> accepted_formats = {
{/*num_channels=*/4,
/*data_type=*/JXL_TYPE_UINT16,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/3,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/3,
/*data_type=*/JXL_TYPE_UINT16,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/1,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
};
JxlBasicInfo info;
JxlEncoderInitBasicInfo(&info);
info.bits_per_sample = 12;
info.num_color_channels = 2;
JxlPixelFormat format;
EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
info.num_color_channels = 3;
ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
EXPECT_EQ(format.num_channels, info.num_color_channels);
// 16 is the smallest accepted format that can accommodate the 12-bit data.
EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
}
TEST(CodecTest, EncodeToPNG) {
ThreadPool* const pool = nullptr;
std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
if (!png_encoder) {
fprintf(stderr, "Skipping test because of missing codec support.\n");
return;
}
const PaddedBytes original_png = jxl::test::ReadTestData(
"external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
PackedPixelFile ppf;
ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
ColorHints(), &ppf));
const JxlPixelFormat& format = ppf.frames.front().color.format;
ASSERT_THAT(
png_encoder->AcceptedFormats(),
Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
Field(&JxlPixelFormat::data_type, format.data_type),
Field(&JxlPixelFormat::endianness, format.endianness))));
EncodedImage encoded_png;
ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
EXPECT_THAT(encoded_png.icc, IsEmpty());
ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
PackedPixelFile decoded_ppf;
ASSERT_TRUE(
extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
ColorHints(), &decoded_ppf));
ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
ASSERT_EQ(decoded_ppf.frames.size(), 1);
VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
decoded_ppf.frames[0].color,
decoded_ppf.info.bits_per_sample);
}
} // namespace
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,987 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/apng.h"
// Parts of this code are taken from apngdis, which has the following license:
/* APNG Disassembler 2.8
*
* Deconstructs APNG files into individual frames.
*
* http://apngdis.sourceforge.net
*
* Copyright (c) 2010-2015 Max Stepin
* maxst at users.sourceforge.net
*
* zlib license
* ------------
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
*/
#include <jxl/codestream_header.h>
#include <jxl/encode.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <utility>
#include <vector>
#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/scope_guard.h"
#include "lib/jxl/common.h"
#include "lib/jxl/sanitizers.h"
#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
#endif
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_APNG
namespace {
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
0x66, 0x00, 0x00};
/* hIST chunk tail is not proccesed properly; skip this chunk completely;
see https://github.com/glennrp/libpng/pull/413 */
const png_byte kIgnoredPngChunks[] = {
104, 73, 83, 84, '\0' /* hIST */
};
// Returns floating-point value from the PNG encoding (times 10^5).
static double F64FromU32(const uint32_t x) {
return static_cast<int32_t>(x) * 1E-5;
}
Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
JxlColorEncoding* color_encoding) {
if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
// (PNG uses the same values as ICC.)
if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
color_encoding->white_point = JXL_WHITE_POINT_D65;
color_encoding->primaries = JXL_PRIMARIES_SRGB;
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
color_encoding->rendering_intent =
static_cast<JxlRenderingIntent>(payload[0]);
return true;
}
// If the cICP profile is not fully supported, return false and leave
// color_encoding unmodified.
Status DecodeCICP(const unsigned char* payload, const size_t payload_size,
JxlColorEncoding* color_encoding) {
if (payload_size != 4) return JXL_FAILURE("Wrong cICP size");
JxlColorEncoding color_enc = *color_encoding;
// From https://www.itu.int/rec/T-REC-H.273-202107-I/en
if (payload[0] == 1) {
// IEC 61966-2-1 sRGB
color_enc.primaries = JXL_PRIMARIES_SRGB;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else if (payload[0] == 4) {
// Rec. ITU-R BT.470-6 System M
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 0.67;
color_enc.primaries_red_xy[1] = 0.33;
color_enc.primaries_green_xy[0] = 0.21;
color_enc.primaries_green_xy[1] = 0.71;
color_enc.primaries_blue_xy[0] = 0.14;
color_enc.primaries_blue_xy[1] = 0.08;
color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
color_enc.white_point_xy[0] = 0.310;
color_enc.white_point_xy[1] = 0.316;
} else if (payload[0] == 5) {
// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 0.64;
color_enc.primaries_red_xy[1] = 0.33;
color_enc.primaries_green_xy[0] = 0.29;
color_enc.primaries_green_xy[1] = 0.60;
color_enc.primaries_blue_xy[0] = 0.15;
color_enc.primaries_blue_xy[1] = 0.06;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else if (payload[0] == 6 || payload[0] == 7) {
// SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 0.630;
color_enc.primaries_red_xy[1] = 0.340;
color_enc.primaries_green_xy[0] = 0.310;
color_enc.primaries_green_xy[1] = 0.595;
color_enc.primaries_blue_xy[0] = 0.155;
color_enc.primaries_blue_xy[1] = 0.070;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else if (payload[0] == 8) {
// Generic film (colour filters using Illuminant C)
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 0.681;
color_enc.primaries_red_xy[1] = 0.319;
color_enc.primaries_green_xy[0] = 0.243;
color_enc.primaries_green_xy[1] = 0.692;
color_enc.primaries_blue_xy[0] = 0.145;
color_enc.primaries_blue_xy[1] = 0.049;
color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
color_enc.white_point_xy[0] = 0.310;
color_enc.white_point_xy[1] = 0.316;
} else if (payload[0] == 9) {
// Rec. ITU-R BT.2100-2
color_enc.primaries = JXL_PRIMARIES_2100;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else if (payload[0] == 10) {
// CIE 1931 XYZ
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 1;
color_enc.primaries_red_xy[1] = 0;
color_enc.primaries_green_xy[0] = 0;
color_enc.primaries_green_xy[1] = 1;
color_enc.primaries_blue_xy[0] = 0;
color_enc.primaries_blue_xy[1] = 0;
color_enc.white_point = JXL_WHITE_POINT_E;
} else if (payload[0] == 11) {
// SMPTE RP 431-2 (2011)
color_enc.primaries = JXL_PRIMARIES_P3;
color_enc.white_point = JXL_WHITE_POINT_DCI;
} else if (payload[0] == 12) {
// SMPTE EG 432-1 (2010)
color_enc.primaries = JXL_PRIMARIES_P3;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else if (payload[0] == 22) {
color_enc.primaries = JXL_PRIMARIES_CUSTOM;
color_enc.primaries_red_xy[0] = 0.630;
color_enc.primaries_red_xy[1] = 0.340;
color_enc.primaries_green_xy[0] = 0.295;
color_enc.primaries_green_xy[1] = 0.605;
color_enc.primaries_blue_xy[0] = 0.155;
color_enc.primaries_blue_xy[1] = 0.077;
color_enc.white_point = JXL_WHITE_POINT_D65;
} else {
JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
static_cast<int>(payload[0]));
return false;
}
if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
payload[1] == 15) {
// Rec. ITU-R BT.709-6
color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
} else if (payload[1] == 4) {
// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
color_enc.gamma = 1 / 2.2;
} else if (payload[1] == 5) {
// Rec. ITU-R BT.470-6 System B, G
color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
color_enc.gamma = 1 / 2.8;
} else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
payload[1] == 17 || payload[1] == 18) {
// These codes all match the corresponding JXL enum values
color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
} else {
JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
static_cast<int>(payload[1]));
return false;
}
if (payload[2] != 0) {
JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
static_cast<int>(payload[2]));
return false;
}
if (payload[3] != 1) {
JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
static_cast<int>(payload[3]));
return false;
}
// cICP has no rendering intent, so use the default
color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
*color_encoding = color_enc;
return true;
}
Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
JxlColorEncoding* color_encoding) {
if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
color_encoding->gamma = F64FromU32(LoadBE32(payload));
return true;
}
Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
JxlColorEncoding* color_encoding) {
if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
return true;
}
// Retrieves XMP and EXIF/IPTC from itext and text.
class BlobsReaderPNG {
public:
static Status Decode(const png_text_struct& info, PackedMetadata* metadata) {
// We trust these are properly null-terminated by libpng.
const char* key = info.key;
const char* value = info.text;
if (strstr(key, "XML:com.adobe.xmp")) {
metadata->xmp.resize(strlen(value)); // safe, see above
memcpy(metadata->xmp.data(), value, metadata->xmp.size());
}
std::string type;
std::vector<uint8_t> bytes;
// Handle text chunks annotated with key "Raw profile type ####", with
// #### a type, which may contain metadata.
const char* kKey = "Raw profile type ";
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
return false;
}
if (type == "exif") {
// Remove "Exif\0\0" prefix if present
if (bytes.size() >= sizeof kExifSignature &&
memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) {
bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature);
}
if (!metadata->exif.empty()) {
JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
" bytes)",
metadata->exif.size(), bytes.size());
}
metadata->exif = std::move(bytes);
} else if (type == "iptc") {
// TODO (jon): Deal with IPTC in some way
} else if (type == "8bim") {
// TODO (jon): Deal with 8bim in some way
} else if (type == "xmp") {
if (!metadata->xmp.empty()) {
JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
" bytes)",
metadata->xmp.size(), bytes.size());
}
metadata->xmp = std::move(bytes);
} else {
JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
" bytes",
type.c_str(), bytes.size());
}
return true;
}
private:
// Returns false if invalid.
static JXL_INLINE Status DecodeNibble(const char c,
uint32_t* JXL_RESTRICT nibble) {
if ('a' <= c && c <= 'f') {
*nibble = 10 + c - 'a';
} else if ('0' <= c && c <= '9') {
*nibble = c - '0';
} else {
*nibble = 0;
return JXL_FAILURE("Invalid metadata nibble");
}
JXL_ASSERT(*nibble < 16);
return true;
}
// Returns false if invalid.
static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
uint32_t* JXL_RESTRICT value) {
size_t len = 0;
*value = 0;
while (*pos < end) {
char next = **pos;
if (next >= '0' && next <= '9') {
*value = (*value * 10) + static_cast<uint32_t>(next - '0');
len++;
if (len > 8) {
break;
}
} else {
// Do not consume terminator (non-decimal digit).
break;
}
(*pos)++;
}
if (len == 0 || len > 8) {
return JXL_FAILURE("Failed to parse decimal");
}
return true;
}
// Parses a PNG text chunk with key of the form "Raw profile type ####", with
// #### a type.
// Returns whether it could successfully parse the content.
// We trust key and encoded are null-terminated because they come from
// libpng.
static Status MaybeDecodeBase16(const char* key, const char* encoded,
std::string* type,
std::vector<uint8_t>* bytes) {
const char* encoded_end = encoded + strlen(encoded);
const char* kKey = "Raw profile type ";
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
*type = key + strlen(kKey);
const size_t kMaxTypeLen = 20;
if (type->length() > kMaxTypeLen) return false; // Type too long
// Header: freeform string and number of bytes
// Expected format is:
// \n
// profile name/description\n
// 40\n (the number of bytes after hex-decoding)
// 01234566789abcdef....\n (72 bytes per line max).
// 012345667\n (last line)
const char* pos = encoded;
if (*(pos++) != '\n') return false;
while (pos < encoded_end && *pos != '\n') {
pos++;
}
if (pos == encoded_end) return false;
// We parsed so far a \n, some number of non \n characters and are now
// pointing at a \n.
if (*(pos++) != '\n') return false;
// Skip leading spaces
while (pos < encoded_end && *pos == ' ') {
pos++;
}
uint32_t bytes_to_decode = 0;
JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
// We need 2*bytes for the hex values plus 1 byte every 36 values,
// plus terminal \n for length.
const unsigned long needed_bytes =
bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
bytes_to_decode);
}
JXL_ASSERT(bytes->empty());
bytes->reserve(bytes_to_decode);
// Encoding: base16 with newline after 72 chars.
// pos points to the \n before the first line of hex values.
for (size_t i = 0; i < bytes_to_decode; ++i) {
if (i % 36 == 0) {
if (pos + 1 >= encoded_end) return false; // Truncated base16 1
if (*pos != '\n') return false; // Expected newline
++pos;
}
if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
uint32_t nibble0, nibble1;
JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
pos += 2;
}
if (pos + 1 != encoded_end) return false; // Too many encoded bytes
if (pos[0] != '\n') return false; // Incorrect metadata terminator
return true;
}
};
constexpr bool isAbc(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
constexpr uint32_t kId_IHDR = 0x52444849;
constexpr uint32_t kId_acTL = 0x4C546361;
constexpr uint32_t kId_fcTL = 0x4C546366;
constexpr uint32_t kId_IDAT = 0x54414449;
constexpr uint32_t kId_fdAT = 0x54416466;
constexpr uint32_t kId_IEND = 0x444E4549;
constexpr uint32_t kId_cICP = 0x50434963;
constexpr uint32_t kId_iCCP = 0x50434369;
constexpr uint32_t kId_sRGB = 0x42475273;
constexpr uint32_t kId_gAMA = 0x414D4167;
constexpr uint32_t kId_cHRM = 0x4D524863;
constexpr uint32_t kId_eXIf = 0x66495865;
struct APNGFrame {
std::vector<uint8_t> pixels;
std::vector<uint8_t*> rows;
unsigned int w, h, delay_num, delay_den;
};
struct Reader {
const uint8_t* next;
const uint8_t* last;
bool Read(void* data, size_t len) {
size_t cap = last - next;
size_t to_copy = std::min(cap, len);
memcpy(data, next, to_copy);
next += to_copy;
return (len == to_copy);
}
bool Eof() { return next == last; }
};
const unsigned long cMaxPNGSize = 1000000UL;
const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB
void info_fn(png_structp png_ptr, png_infop info_ptr) {
png_set_expand(png_ptr);
png_set_palette_to_rgb(png_ptr);
png_set_tRNS_to_alpha(png_ptr);
(void)png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
}
void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
int pass) {
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
JXL_CHECK(frame);
JXL_CHECK(row_num < frame->rows.size());
JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size());
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
}
inline unsigned int read_chunk(Reader* r, std::vector<uint8_t>* pChunk) {
unsigned char len[4];
if (r->Read(&len, 4)) {
const auto size = png_get_uint_32(len);
// Check first, to avoid overflow.
if (size > kMaxPNGChunkSize) {
JXL_WARNING("APNG chunk size is too big");
return 0;
}
pChunk->resize(size + 12);
memcpy(pChunk->data(), len, 4);
if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) {
return LoadLE32(pChunk->data() + 4);
}
}
return 0;
}
int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
bool hasInfo, std::vector<uint8_t>& chunkIHDR,
std::vector<std::vector<uint8_t>>& chunksInfo) {
unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
// Cleanup prior decoder, if any.
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
// Just in case. Not all versions on libpng wipe-out the pointers.
png_ptr = nullptr;
info_ptr = nullptr;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
if (!png_ptr || !info_ptr) return 1;
if (setjmp(png_jmpbuf(png_ptr))) {
return 1;
}
png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks,
(int)sizeof(kIgnoredPngChunks) / 5);
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL);
png_process_data(png_ptr, info_ptr, header, 8);
png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size());
if (hasInfo) {
for (unsigned int i = 0; i < chunksInfo.size(); i++) {
png_process_data(png_ptr, info_ptr, chunksInfo[i].data(),
chunksInfo[i].size());
}
}
return 0;
}
int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
unsigned int size) {
if (!png_ptr || !info_ptr) return 1;
if (setjmp(png_jmpbuf(png_ptr))) {
return 1;
}
png_process_data(png_ptr, info_ptr, p, size);
return 0;
}
int processing_finish(png_structp png_ptr, png_infop info_ptr,
PackedMetadata* metadata) {
unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
if (!png_ptr || !info_ptr) return 1;
if (setjmp(png_jmpbuf(png_ptr))) {
return 1;
}
png_process_data(png_ptr, info_ptr, footer, 12);
// before destroying: check if we encountered any metadata chunks
png_textp text_ptr;
int num_text;
png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
for (int i = 0; i < num_text; i++) {
(void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
}
return 0;
}
} // namespace
#endif
bool CanDecodeAPNG() {
#if JPEGXL_ENABLE_APNG
return true;
#else
return false;
#endif
}
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
const ColorHints& color_hints, PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_APNG
Reader r;
unsigned int id, j, w, h, w0, h0, x0, y0;
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
unsigned char sig[8];
png_structp png_ptr = nullptr;
png_infop info_ptr = nullptr;
std::vector<uint8_t> chunk;
std::vector<uint8_t> chunkIHDR;
std::vector<std::vector<uint8_t>> chunksInfo;
bool isAnimated = false;
bool hasInfo = false;
bool seenFctl = false;
APNGFrame frameRaw = {};
uint32_t num_channels;
JxlPixelFormat format;
unsigned int bytes_per_pixel = 0;
struct FrameInfo {
PackedImage data;
uint32_t duration;
size_t x0, xsize;
size_t y0, ysize;
uint32_t dispose_op;
uint32_t blend_op;
};
std::vector<FrameInfo> frames;
// Make sure png memory is released in any case.
auto scope_guard = MakeScopeGuard([&]() {
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
// Just in case. Not all versions on libpng wipe-out the pointers.
png_ptr = nullptr;
info_ptr = nullptr;
});
r = {bytes.data(), bytes.data() + bytes.size()};
// Not a PNG => not an error
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
return false;
}
id = read_chunk(&r, &chunkIHDR);
ppf->info.exponent_bits_per_sample = 0;
ppf->info.alpha_exponent_bits = 0;
ppf->info.orientation = JXL_ORIENT_IDENTITY;
ppf->frames.clear();
bool have_color = false;
bool have_cicp = false, have_iccp = false, have_srgb = false;
bool errorstate = true;
if (id == kId_IHDR && chunkIHDR.size() == 25) {
x0 = 0;
y0 = 0;
delay_num = 1;
delay_den = 10;
dop = 0;
bop = 0;
w0 = w = png_get_uint_32(chunkIHDR.data() + 8);
h0 = h = png_get_uint_32(chunkIHDR.data() + 12);
if (w > cMaxPNGSize || h > cMaxPNGSize) {
return false;
}
// default settings in case e.g. only gAMA is given
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
chunkIHDR, chunksInfo)) {
while (!r.Eof()) {
id = read_chunk(&r, &chunk);
if (!id) break;
seenFctl |= (id == kId_fcTL);
if (id == kId_acTL && !hasInfo && !isAnimated) {
isAnimated = true;
ppf->info.have_animation = true;
ppf->info.animation.tps_numerator = 1000;
ppf->info.animation.tps_denominator = 1;
} else if (id == kId_IEND ||
(id == kId_fcTL && (!hasInfo || isAnimated))) {
if (hasInfo) {
if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
// Allocates the frame buffer.
uint32_t duration = delay_num * 1000 / delay_den;
frames.push_back(FrameInfo{PackedImage(w0, h0, format), duration,
x0, w0, y0, h0, dop, bop});
auto& frame = frames.back().data;
for (size_t y = 0; y < h0; ++y) {
memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
frameRaw.rows[y], bytes_per_pixel * w0);
}
} else {
break;
}
}
if (id == kId_IEND) {
errorstate = false;
break;
}
if (chunk.size() < 34) {
return JXL_FAILURE("Received a chunk that is too small (%" PRIuS
"B)",
chunk.size());
}
// At this point the old frame is done. Let's start a new one.
w0 = png_get_uint_32(chunk.data() + 12);
h0 = png_get_uint_32(chunk.data() + 16);
x0 = png_get_uint_32(chunk.data() + 20);
y0 = png_get_uint_32(chunk.data() + 24);
delay_num = png_get_uint_16(chunk.data() + 28);
delay_den = png_get_uint_16(chunk.data() + 30);
dop = chunk[32];
bop = chunk[33];
if (!delay_den) delay_den = 100;
if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
bop > 1) {
break;
}
if (hasInfo) {
memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8);
if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
chunkIHDR, chunksInfo)) {
break;
}
}
} else if (id == kId_IDAT) {
// First IDAT chunk means we now have all header info
if (seenFctl) {
// `fcTL` chunk must appear after all `IDAT` chunks
return JXL_FAILURE("IDAT chunk after fcTL chunk");
}
hasInfo = true;
JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
int colortype = png_get_color_type(png_ptr, info_ptr);
int png_bit_depth = png_get_bit_depth(png_ptr, info_ptr);
ppf->info.bits_per_sample = png_bit_depth;
png_color_8p sigbits = NULL;
png_get_sBIT(png_ptr, info_ptr, &sigbits);
if (colortype & 1) {
// palette will actually be 8-bit regardless of the index bitdepth
ppf->info.bits_per_sample = 8;
}
if (colortype & 2) {
ppf->info.num_color_channels = 3;
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
if (sigbits && sigbits->red == sigbits->green &&
sigbits->green == sigbits->blue)
ppf->info.bits_per_sample = sigbits->red;
} else {
ppf->info.num_color_channels = 1;
ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
}
if (colortype & 4 ||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
ppf->info.alpha_bits = ppf->info.bits_per_sample;
if (sigbits) {
if (sigbits->alpha &&
sigbits->alpha != ppf->info.bits_per_sample) {
return JXL_FAILURE("Unsupported alpha bit-depth");
}
ppf->info.alpha_bits = sigbits->alpha;
}
} else {
ppf->info.alpha_bits = 0;
}
ppf->color_encoding.color_space =
(ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
: JXL_COLOR_SPACE_RGB);
ppf->info.xsize = w;
ppf->info.ysize = h;
JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
num_channels =
ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
format = {
/*num_channels=*/num_channels,
/*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
: JXL_TYPE_UINT8,
/*endianness=*/JXL_BIG_ENDIAN,
/*align=*/0,
};
if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) {
png_set_strip_16(png_ptr);
}
bytes_per_pixel =
num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
rowbytes = w * bytes_per_pixel;
imagesize = h * rowbytes;
frameRaw.pixels.resize(imagesize);
frameRaw.rows.resize(h);
for (j = 0; j < h; j++)
frameRaw.rows[j] = frameRaw.pixels.data() + j * rowbytes;
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
break;
}
} else if (id == kId_fdAT && isAnimated) {
if (!hasInfo) {
return JXL_FAILURE("fDAT chunk before iDAT");
}
png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
memcpy(chunk.data() + 8, "IDAT", 4);
if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
chunk.size() - 4)) {
break;
}
} else if (id == kId_cICP) {
// Color profile chunks: cICP has the highest priority, followed by
// iCCP and sRGB (which shouldn't co-exist, but if they do, we use
// iCCP), followed finally by gAMA and cHRM.
if (DecodeCICP(chunk.data() + 8, chunk.size() - 12,
&ppf->color_encoding)) {
have_cicp = true;
have_color = true;
ppf->icc.clear();
}
} else if (!have_cicp && id == kId_iCCP) {
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
JXL_WARNING("Corrupt iCCP chunk");
break;
}
// TODO(jon): catch special case of PQ and synthesize color encoding
// in that case
int compression_type;
png_bytep profile;
png_charp name;
png_uint_32 proflen = 0;
auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type,
&profile, &proflen);
if (ok && proflen) {
ppf->icc.assign(profile, profile + proflen);
have_color = true;
have_iccp = true;
} else {
// TODO(eustas): JXL_WARNING?
}
} else if (!have_cicp && !have_iccp && id == kId_sRGB) {
JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12,
&ppf->color_encoding));
have_srgb = true;
have_color = true;
} else if (!have_cicp && !have_srgb && !have_iccp && id == kId_gAMA) {
JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12,
&ppf->color_encoding));
have_color = true;
} else if (!have_cicp && !have_srgb && !have_iccp && id == kId_cHRM) {
JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12,
&ppf->color_encoding));
have_color = true;
} else if (id == kId_eXIf) {
ppf->metadata.exif.resize(chunk.size() - 12);
memcpy(ppf->metadata.exif.data(), chunk.data() + 8,
chunk.size() - 12);
} else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
!isAbc(chunk[7])) {
break;
} else {
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
break;
}
if (!hasInfo) {
chunksInfo.push_back(chunk);
continue;
}
}
}
}
JXL_RETURN_IF_ERROR(ApplyColorHints(
color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
}
if (errorstate) return false;
bool has_nontrivial_background = false;
bool previous_frame_should_be_cleared = false;
enum {
DISPOSE_OP_NONE = 0,
DISPOSE_OP_BACKGROUND = 1,
DISPOSE_OP_PREVIOUS = 2,
};
enum {
BLEND_OP_SOURCE = 0,
BLEND_OP_OVER = 1,
};
for (size_t i = 0; i < frames.size(); i++) {
auto& frame = frames[i];
JXL_ASSERT(frame.data.xsize == frame.xsize);
JXL_ASSERT(frame.data.ysize == frame.ysize);
// Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with 0,
// so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
if (frame.dispose_op == DISPOSE_OP_NONE) {
has_nontrivial_background = true;
}
bool should_blend = frame.blend_op == BLEND_OP_OVER;
bool use_for_next_frame =
has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS;
size_t x0 = frame.x0;
size_t y0 = frame.y0;
size_t xsize = frame.data.xsize;
size_t ysize = frame.data.ysize;
if (previous_frame_should_be_cleared) {
size_t px0 = frames[i - 1].x0;
size_t py0 = frames[i - 1].y0;
size_t pxs = frames[i - 1].xsize;
size_t pys = frames[i - 1].ysize;
if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
py0 + pys <= y0 + ysize && frame.blend_op == BLEND_OP_SOURCE &&
use_for_next_frame) {
// If the previous frame is entirely contained in the current frame and
// we are using BLEND_OP_SOURCE, nothing special needs to be done.
ppf->frames.emplace_back(std::move(frame.data));
} else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
py0 + pys == y0 + ysize && use_for_next_frame) {
// If the new frame has the same size as the old one, but we are
// blending, we can instead just not blend.
should_blend = false;
ppf->frames.emplace_back(std::move(frame.data));
} else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
py0 + pys >= y0 + ysize && use_for_next_frame) {
// If the new frame is contained within the old frame, we can pad the
// new frame with zeros and not blend.
PackedImage new_data(pxs, pys, frame.data.format);
memset(new_data.pixels(), 0, new_data.pixels_size);
for (size_t y = 0; y < ysize; y++) {
size_t bytes_per_pixel =
PackedImage::BitsPerChannel(new_data.format.data_type) *
new_data.format.num_channels / 8;
memcpy(static_cast<uint8_t*>(new_data.pixels()) +
new_data.stride * (y + y0 - py0) +
bytes_per_pixel * (x0 - px0),
static_cast<const uint8_t*>(frame.data.pixels()) +
frame.data.stride * y,
xsize * bytes_per_pixel);
}
x0 = px0;
y0 = py0;
xsize = pxs;
ysize = pys;
should_blend = false;
ppf->frames.emplace_back(std::move(new_data));
} else {
// If all else fails, insert a dummy blank frame with kReplace.
PackedImage blank(pxs, pys, frame.data.format);
memset(blank.pixels(), 0, blank.pixels_size);
ppf->frames.emplace_back(std::move(blank));
auto& pframe = ppf->frames.back();
pframe.frame_info.layer_info.crop_x0 = px0;
pframe.frame_info.layer_info.crop_y0 = py0;
pframe.frame_info.layer_info.xsize = pxs;
pframe.frame_info.layer_info.ysize = pys;
pframe.frame_info.duration = 0;
bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
pys == ppf->info.ysize;
pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
pframe.frame_info.layer_info.blend_info.source = 1;
pframe.frame_info.layer_info.save_as_reference = 1;
ppf->frames.emplace_back(std::move(frame.data));
}
} else {
ppf->frames.emplace_back(std::move(frame.data));
}
auto& pframe = ppf->frames.back();
pframe.frame_info.layer_info.crop_x0 = x0;
pframe.frame_info.layer_info.crop_y0 = y0;
pframe.frame_info.layer_info.xsize = xsize;
pframe.frame_info.layer_info.ysize = ysize;
pframe.frame_info.duration = frame.duration;
pframe.frame_info.layer_info.blend_info.blendmode =
should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
ysize == ppf->info.ysize;
pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
pframe.frame_info.layer_info.blend_info.source = 1;
pframe.frame_info.layer_info.blend_info.alpha = 0;
pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
previous_frame_should_be_cleared =
has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND;
}
if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
ppf->frames.back().frame_info.is_last = true;
return true;
#else
return false;
#endif
}
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,36 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_DEC_APNG_H_
#define LIB_EXTRAS_DEC_APNG_H_
// Decodes APNG images in memory.
#include <stdint.h>
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
namespace jxl {
struct SizeConstraints;
namespace extras {
bool CanDecodeAPNG();
// Decodes `bytes` into `ppf`.
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_DEC_APNG_H_

View File

@ -0,0 +1,218 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/color_description.h"
#include <errno.h>
#include <cmath>
namespace jxl {
namespace {
template <typename T>
struct EnumName {
const char* name;
T value;
};
const EnumName<JxlColorSpace> kJxlColorSpaceNames[] = {
{"RGB", JXL_COLOR_SPACE_RGB},
{"Gra", JXL_COLOR_SPACE_GRAY},
{"XYB", JXL_COLOR_SPACE_XYB},
{"CS?", JXL_COLOR_SPACE_UNKNOWN},
};
const EnumName<JxlWhitePoint> kJxlWhitePointNames[] = {
{"D65", JXL_WHITE_POINT_D65},
{"Cst", JXL_WHITE_POINT_CUSTOM},
{"EER", JXL_WHITE_POINT_E},
{"DCI", JXL_WHITE_POINT_DCI},
};
const EnumName<JxlPrimaries> kJxlPrimariesNames[] = {
{"SRG", JXL_PRIMARIES_SRGB},
{"Cst", JXL_PRIMARIES_CUSTOM},
{"202", JXL_PRIMARIES_2100},
{"DCI", JXL_PRIMARIES_P3},
};
const EnumName<JxlTransferFunction> kJxlTransferFunctionNames[] = {
{"709", JXL_TRANSFER_FUNCTION_709},
{"TF?", JXL_TRANSFER_FUNCTION_UNKNOWN},
{"Lin", JXL_TRANSFER_FUNCTION_LINEAR},
{"SRG", JXL_TRANSFER_FUNCTION_SRGB},
{"PeQ", JXL_TRANSFER_FUNCTION_PQ},
{"DCI", JXL_TRANSFER_FUNCTION_DCI},
{"HLG", JXL_TRANSFER_FUNCTION_HLG},
{"", JXL_TRANSFER_FUNCTION_GAMMA},
};
const EnumName<JxlRenderingIntent> kJxlRenderingIntentNames[] = {
{"Per", JXL_RENDERING_INTENT_PERCEPTUAL},
{"Rel", JXL_RENDERING_INTENT_RELATIVE},
{"Sat", JXL_RENDERING_INTENT_SATURATION},
{"Abs", JXL_RENDERING_INTENT_ABSOLUTE},
};
template <typename T>
Status ParseEnum(const std::string& token, const EnumName<T>* enum_values,
size_t enum_len, T* value) {
for (size_t i = 0; i < enum_len; i++) {
if (enum_values[i].name == token) {
*value = enum_values[i].value;
return true;
}
}
return false;
}
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
#define PARSE_ENUM(type, token, value) \
ParseEnum<type>(token, k##type##Names, ARRAY_SIZE(k##type##Names), value)
class Tokenizer {
public:
Tokenizer(const std::string* input, char separator)
: input_(input), separator_(separator) {}
Status Next(std::string* next) {
const size_t end = input_->find(separator_, start_);
if (end == std::string::npos) {
*next = input_->substr(start_); // rest of string
} else {
*next = input_->substr(start_, end - start_);
}
if (next->empty()) return JXL_FAILURE("Missing token");
start_ = end + 1;
return true;
}
private:
const std::string* const input_; // not owned
const char separator_;
size_t start_ = 0; // of next token
};
Status ParseDouble(const std::string& num, double* d) {
char* end;
errno = 0;
*d = strtod(num.c_str(), &end);
if (*d == 0.0 && end == num.c_str()) {
return JXL_FAILURE("Invalid double: %s", num.c_str());
}
if (std::isnan(*d)) {
return JXL_FAILURE("Invalid double: %s", num.c_str());
}
if (errno == ERANGE) {
return JXL_FAILURE("Double out of range: %s", num.c_str());
}
return true;
}
Status ParseDouble(Tokenizer* tokenizer, double* d) {
std::string num;
JXL_RETURN_IF_ERROR(tokenizer->Next(&num));
return ParseDouble(num, d);
}
Status ParseColorSpace(Tokenizer* tokenizer, JxlColorEncoding* c) {
std::string str;
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
JxlColorSpace cs;
if (PARSE_ENUM(JxlColorSpace, str, &cs)) {
c->color_space = cs;
return true;
}
return JXL_FAILURE("Unknown ColorSpace %s", str.c_str());
}
Status ParseWhitePoint(Tokenizer* tokenizer, JxlColorEncoding* c) {
if (c->color_space == JXL_COLOR_SPACE_XYB) {
// Implicit white point.
c->white_point = JXL_WHITE_POINT_D65;
return true;
}
std::string str;
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
if (PARSE_ENUM(JxlWhitePoint, str, &c->white_point)) return true;
Tokenizer xy_tokenizer(&str, ';');
c->white_point = JXL_WHITE_POINT_CUSTOM;
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 0));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 1));
return true;
}
Status ParsePrimaries(Tokenizer* tokenizer, JxlColorEncoding* c) {
if (c->color_space == JXL_COLOR_SPACE_GRAY ||
c->color_space == JXL_COLOR_SPACE_XYB) {
// No primaries case.
return true;
}
std::string str;
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
if (PARSE_ENUM(JxlPrimaries, str, &c->primaries)) return true;
Tokenizer xy_tokenizer(&str, ';');
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 0));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 1));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 0));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 1));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 0));
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 1));
c->primaries = JXL_PRIMARIES_CUSTOM;
return JXL_FAILURE("Invalid primaries %s", str.c_str());
}
Status ParseRenderingIntent(Tokenizer* tokenizer, JxlColorEncoding* c) {
std::string str;
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
if (PARSE_ENUM(JxlRenderingIntent, str, &c->rendering_intent)) return true;
return JXL_FAILURE("Invalid RenderingIntent %s\n", str.c_str());
}
Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) {
if (c->color_space == JXL_COLOR_SPACE_XYB) {
// Implicit TF.
c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
c->gamma = 1 / 3.;
return true;
}
std::string str;
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
if (PARSE_ENUM(JxlTransferFunction, str, &c->transfer_function)) {
return true;
}
if (str[0] == 'g') {
JXL_RETURN_IF_ERROR(ParseDouble(str.substr(1), &c->gamma));
c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
return true;
}
return JXL_FAILURE("Invalid gamma %s", str.c_str());
}
} // namespace
Status ParseDescription(const std::string& description, JxlColorEncoding* c) {
*c = {};
Tokenizer tokenizer(&description, '_');
JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c));
JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c));
JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c));
JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c));
JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c));
return true;
}
} // namespace jxl

View File

@ -0,0 +1,23 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_COLOR_DESCRIPTION_H_
#define LIB_EXTRAS_COLOR_DESCRIPTION_H_
#include <jxl/color_encoding.h>
#include <string>
#include "lib/jxl/base/status.h"
namespace jxl {
// Parse the color description into a JxlColorEncoding "RGB_D65_SRG_Rel_Lin".
Status ParseDescription(const std::string& description,
JxlColorEncoding* JXL_RESTRICT c);
} // namespace jxl
#endif // LIB_EXTRAS_COLOR_DESCRIPTION_H_

View File

@ -0,0 +1,38 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/color_description.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"
namespace jxl {
// Verify ParseDescription(Description) yields the same ColorEncoding
TEST(ColorDescriptionTest, RoundTripAll) {
for (const auto& cdesc : test::AllEncodings()) {
const ColorEncoding c_original = test::ColorEncodingFromDescriptor(cdesc);
const std::string description = Description(c_original);
printf("%s\n", description.c_str());
JxlColorEncoding c_external = {};
EXPECT_TRUE(ParseDescription(description, &c_external));
ColorEncoding c_internal;
EXPECT_TRUE(
ConvertExternalToInternalColorEncoding(c_external, &c_internal));
EXPECT_TRUE(c_original.SameColorEncoding(c_internal))
<< "Where c_original=" << c_original
<< " and c_internal=" << c_internal;
}
}
TEST(ColorDescriptionTest, NanGamma) {
const std::string description = "Gra_2_Per_gnan";
JxlColorEncoding c;
EXPECT_FALSE(ParseDescription(description, &c));
}
} // namespace jxl

View File

@ -0,0 +1,78 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/color_hints.h"
#include <jxl/encode.h>
#include <vector>
#include "lib/extras/dec/color_description.h"
#include "lib/jxl/base/status.h"
namespace jxl {
namespace extras {
Status ApplyColorHints(const ColorHints& color_hints,
const bool color_already_set, const bool is_gray,
PackedPixelFile* ppf) {
bool got_color_space = color_already_set;
JXL_RETURN_IF_ERROR(color_hints.Foreach(
[color_already_set, is_gray, ppf, &got_color_space](
const std::string& key, const std::string& value) -> Status {
if (color_already_set && (key == "color_space" || key == "icc")) {
JXL_WARNING("Decoder ignoring %s hint", key.c_str());
return true;
}
if (key == "color_space") {
JxlColorEncoding c_original_external;
if (!ParseDescription(value, &c_original_external)) {
return JXL_FAILURE("Failed to apply color_space");
}
ppf->color_encoding = c_original_external;
if (is_gray !=
(ppf->color_encoding.color_space == JXL_COLOR_SPACE_GRAY)) {
return JXL_FAILURE("mismatch between file and color_space hint");
}
got_color_space = true;
} else if (key == "icc") {
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
std::vector<uint8_t> icc(data, data + value.size());
ppf->icc.swap(icc);
got_color_space = true;
} else if (key == "exif") {
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
std::vector<uint8_t> blob(data, data + value.size());
ppf->metadata.exif.swap(blob);
} else if (key == "xmp") {
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
std::vector<uint8_t> blob(data, data + value.size());
ppf->metadata.xmp.swap(blob);
} else if (key == "jumbf") {
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
std::vector<uint8_t> blob(data, data + value.size());
ppf->metadata.jumbf.swap(blob);
} else {
JXL_WARNING("Ignoring %s hint", key.c_str());
}
return true;
}));
if (!got_color_space) {
ppf->color_encoding.color_space =
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
}
return true;
}
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,74 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_COLOR_HINTS_H_
#define LIB_EXTRAS_COLOR_HINTS_H_
// Not all the formats implemented in the extras lib support bundling color
// information into the file, and those that support it may not have it.
// To allow attaching color information to those file formats the caller can
// define these color hints.
// Besides color space information, 'ColorHints' may also include other
// additional information such as Exif, XMP and JUMBF metadata.
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/status.h"
namespace jxl {
namespace extras {
class ColorHints {
public:
// key=color_space, value=Description(c/pp): specify the ColorEncoding of
// the pixels for decoding. Otherwise, if the codec did not obtain an ICC
// profile from the image, assume sRGB.
//
// Strings are taken from the command line, so avoid spaces for convenience.
void Add(const std::string& key, const std::string& value) {
kv_.emplace_back(key, value);
}
// Calls `func(key, value)` for each key/value in the order they were added,
// returning false immediately if `func` returns false.
template <class Func>
Status Foreach(const Func& func) const {
for (const KeyValue& kv : kv_) {
Status ok = func(kv.key, kv.value);
if (!ok) {
return JXL_FAILURE("ColorHints::Foreach returned false");
}
}
return true;
}
private:
// Splitting into key/value avoids parsing in each codec.
struct KeyValue {
KeyValue(std::string key, std::string value)
: key(std::move(key)), value(std::move(value)) {}
std::string key;
std::string value;
};
std::vector<KeyValue> kv_;
};
// Apply the color hints to the decoded image in PackedPixelFile if any.
// color_already_set tells whether the color encoding was already set, in which
// case the hints are ignored if any hint is passed.
Status ApplyColorHints(const ColorHints& color_hints, bool color_already_set,
bool is_gray, PackedPixelFile* ppf);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_COLOR_HINTS_H_

View File

@ -0,0 +1,156 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/decode.h"
#include <locale>
#include "lib/extras/dec/apng.h"
#include "lib/extras/dec/exr.h"
#include "lib/extras/dec/gif.h"
#include "lib/extras/dec/jpg.h"
#include "lib/extras/dec/jxl.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
namespace jxl {
namespace extras {
namespace {
// Any valid encoding is larger (ensures codecs can read the first few bytes)
constexpr size_t kMinBytes = 9;
void BasenameAndExtension(std::string path, std::string* basename,
std::string* extension) {
// Pattern: file.jxl
size_t pos = path.find_last_of('.');
if (pos < path.size()) {
*basename = path.substr(0, pos);
*extension = path.substr(pos);
return;
}
// Pattern: jxl:-
pos = path.find_first_of(':');
if (pos < path.size()) {
*basename = path.substr(pos + 1);
*extension = "." + path.substr(0, pos);
return;
}
// Extension not found
*basename = path;
*extension = "";
}
} // namespace
Codec CodecFromPath(std::string path, size_t* JXL_RESTRICT bits_per_sample,
std::string* basename, std::string* extension) {
std::string base;
std::string ext;
BasenameAndExtension(path, &base, &ext);
if (basename) *basename = base;
if (extension) *extension = ext;
std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) {
return std::tolower(c, std::locale::classic());
});
if (ext == ".png") return Codec::kPNG;
if (ext == ".jpg") return Codec::kJPG;
if (ext == ".jpeg") return Codec::kJPG;
if (ext == ".pgx") return Codec::kPGX;
if (ext == ".pam") return Codec::kPNM;
if (ext == ".pnm") return Codec::kPNM;
if (ext == ".pgm") return Codec::kPNM;
if (ext == ".ppm") return Codec::kPNM;
if (ext == ".pfm") {
if (bits_per_sample != nullptr) *bits_per_sample = 32;
return Codec::kPNM;
}
if (ext == ".gif") return Codec::kGIF;
if (ext == ".exr") return Codec::kEXR;
return Codec::kUnknown;
}
bool CanDecode(Codec codec) {
switch (codec) {
case Codec::kEXR:
return CanDecodeEXR();
case Codec::kGIF:
return CanDecodeGIF();
case Codec::kJPG:
return CanDecodeJPG();
case Codec::kPNG:
return CanDecodeAPNG();
case Codec::kPNM:
case Codec::kPGX:
case Codec::kJXL:
return true;
default:
return false;
}
}
Status DecodeBytes(const Span<const uint8_t> bytes,
const ColorHints& color_hints, extras::PackedPixelFile* ppf,
const SizeConstraints* constraints, Codec* orig_codec) {
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
*ppf = extras::PackedPixelFile();
// Default values when not set by decoders.
ppf->info.uses_original_profile = true;
ppf->info.orientation = JXL_ORIENT_IDENTITY;
const auto choose_codec = [&]() -> Codec {
if (DecodeImageAPNG(bytes, color_hints, ppf, constraints)) {
return Codec::kPNG;
}
if (DecodeImagePGX(bytes, color_hints, ppf, constraints)) {
return Codec::kPGX;
}
if (DecodeImagePNM(bytes, color_hints, ppf, constraints)) {
return Codec::kPNM;
}
JXLDecompressParams dparams = {};
for (const uint32_t num_channels : {1, 2, 3, 4}) {
dparams.accepted_formats.push_back(
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
}
size_t decoded_bytes;
if (DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes,
ppf) &&
ApplyColorHints(color_hints, true, ppf->info.num_color_channels == 1,
ppf)) {
return Codec::kJXL;
}
if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
return Codec::kGIF;
}
if (DecodeImageJPG(bytes, color_hints, ppf, constraints)) {
return Codec::kJPG;
}
if (DecodeImageEXR(bytes, color_hints, ppf, constraints)) {
return Codec::kEXR;
}
return Codec::kUnknown;
};
Codec codec = choose_codec();
if (codec == Codec::kUnknown) {
return JXL_FAILURE("Codecs failed to decode");
}
if (orig_codec) *orig_codec = codec;
return true;
}
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,58 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_DEC_DECODE_H_
#define LIB_EXTRAS_DEC_DECODE_H_
// Facade for image decoders (PNG, PNM, ...).
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "lib/extras/dec/color_hints.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
namespace jxl {
struct SizeConstraints;
namespace extras {
// Codecs supported by DecodeBytes.
enum class Codec : uint32_t {
kUnknown, // for CodecFromPath
kPNG,
kPNM,
kPGX,
kJPG,
kGIF,
kEXR,
kJXL
};
bool CanDecode(Codec codec);
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.
Codec CodecFromPath(std::string path,
size_t* JXL_RESTRICT bits_per_sample = nullptr,
std::string* basename = nullptr,
std::string* extension = nullptr);
// Decodes "bytes" info *ppf.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
extras::PackedPixelFile* ppf,
const SizeConstraints* constraints = nullptr,
Codec* orig_codec = nullptr);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_DEC_DECODE_H_

View File

@ -0,0 +1,201 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/exr.h"
#if JPEGXL_ENABLE_EXR
#include <ImfChromaticitiesAttribute.h>
#include <ImfIO.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#endif
#include <vector>
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_EXR
namespace {
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
// uint64_t as recommended causes build failures with previous OpenEXR versions
// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
// to uint64_t. This alternative should work in all cases.
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
constexpr int kExrBitsPerSample = 16;
constexpr int kExrAlphaBits = 16;
class InMemoryIStream : public OpenEXR::IStream {
public:
// The data pointed to by `bytes` must outlive the InMemoryIStream.
explicit InMemoryIStream(const Span<const uint8_t> bytes)
: IStream(/*fileName=*/""), bytes_(bytes) {}
bool isMemoryMapped() const override { return true; }
char* readMemoryMapped(const int n) override {
JXL_ASSERT(pos_ + n <= bytes_.size());
char* const result =
const_cast<char*>(reinterpret_cast<const char*>(bytes_.data() + pos_));
pos_ += n;
return result;
}
bool read(char c[], const int n) override {
std::copy_n(readMemoryMapped(n), n, c);
return pos_ < bytes_.size();
}
ExrInt64 tellg() override { return pos_; }
void seekg(const ExrInt64 pos) override {
JXL_ASSERT(pos + 1 <= bytes_.size());
pos_ = pos;
}
private:
const Span<const uint8_t> bytes_;
size_t pos_ = 0;
};
} // namespace
#endif
bool CanDecodeEXR() {
#if JPEGXL_ENABLE_EXR
return true;
#else
return false;
#endif
}
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_EXR
InMemoryIStream is(bytes);
#ifdef __EXCEPTIONS
std::unique_ptr<OpenEXR::RgbaInputFile> input_ptr;
try {
input_ptr.reset(new OpenEXR::RgbaInputFile(is));
} catch (...) {
// silently return false if it is not an EXR file
return false;
}
OpenEXR::RgbaInputFile& input = *input_ptr;
#else
OpenEXR::RgbaInputFile input(is);
#endif
if ((input.channels() & OpenEXR::RgbaChannels::WRITE_RGB) !=
OpenEXR::RgbaChannels::WRITE_RGB) {
return JXL_FAILURE("only RGB OpenEXR files are supported");
}
const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) ==
OpenEXR::RgbaChannels::WRITE_A;
const float intensity_target = OpenEXR::hasWhiteLuminance(input.header())
? OpenEXR::whiteLuminance(input.header())
: 0;
auto image_size = input.displayWindow().size();
// Size is computed as max - min, but both bounds are inclusive.
++image_size.x;
++image_size.y;
ppf->info.xsize = image_size.x;
ppf->info.ysize = image_size.y;
ppf->info.num_color_channels = 3;
const JxlDataType data_type =
kExrBitsPerSample == 16 ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT;
const JxlPixelFormat format{
/*num_channels=*/3u + (has_alpha ? 1u : 0u),
/*data_type=*/data_type,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0,
};
ppf->frames.clear();
// Allocates the frame buffer.
ppf->frames.emplace_back(image_size.x, image_size.y, format);
const auto& frame = ppf->frames.back();
const int row_size = input.dataWindow().size().x + 1;
// Number of rows to read at a time.
// https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf
// recommends reading the whole file at once.
const int y_chunk_size = input.displayWindow().size().y + 1;
std::vector<OpenEXR::Rgba> input_rows(row_size * y_chunk_size);
for (int start_y =
std::max(input.dataWindow().min.y, input.displayWindow().min.y);
start_y <=
std::min(input.dataWindow().max.y, input.displayWindow().max.y);
start_y += y_chunk_size) {
// Inclusive.
const int end_y = std::min(
start_y + y_chunk_size - 1,
std::min(input.dataWindow().max.y, input.displayWindow().max.y));
input.setFrameBuffer(
input_rows.data() - input.dataWindow().min.x - start_y * row_size,
/*xStride=*/1, /*yStride=*/row_size);
input.readPixels(start_y, end_y);
for (int exr_y = start_y; exr_y <= end_y; ++exr_y) {
const int image_y = exr_y - input.displayWindow().min.y;
const OpenEXR::Rgba* const JXL_RESTRICT input_row =
&input_rows[(exr_y - start_y) * row_size];
uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) +
frame.color.stride * image_y;
const uint32_t pixel_size =
(3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8;
for (int exr_x =
std::max(input.dataWindow().min.x, input.displayWindow().min.x);
exr_x <=
std::min(input.dataWindow().max.x, input.displayWindow().max.x);
++exr_x) {
const int image_x = exr_x - input.displayWindow().min.x;
// TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable
memcpy(row + image_x * pixel_size,
input_row + (exr_x - input.dataWindow().min.x), pixel_size);
}
}
}
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
if (OpenEXR::hasChromaticities(input.header())) {
ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM;
ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM;
const auto& chromaticities = OpenEXR::chromaticities(input.header());
ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x;
ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y;
ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x;
ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y;
ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x;
ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y;
ppf->color_encoding.white_point_xy[0] = chromaticities.white.x;
ppf->color_encoding.white_point_xy[1] = chromaticities.white.y;
}
// EXR uses binary16 or binary32 floating point format.
ppf->info.bits_per_sample = kExrBitsPerSample;
ppf->info.exponent_bits_per_sample = kExrBitsPerSample == 16 ? 5 : 8;
if (has_alpha) {
ppf->info.alpha_bits = kExrAlphaBits;
ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample;
ppf->info.alpha_premultiplied = true;
}
ppf->info.intensity_target = intensity_target;
return true;
#else
return false;
#endif
}
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,34 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_DEC_EXR_H_
#define LIB_EXTRAS_DEC_EXR_H_
// Decodes OpenEXR images in memory.
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
namespace jxl {
struct SizeConstraints;
namespace extras {
bool CanDecodeEXR();
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_DEC_EXR_H_

View File

@ -0,0 +1,415 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/dec/gif.h"
#if JPEGXL_ENABLE_GIF
#include <gif_lib.h>
#endif
#include <jxl/codestream_header.h>
#include <string.h>
#include <memory>
#include <utility>
#include <vector>
#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/sanitizers.h"
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_GIF
namespace {
struct ReadState {
Span<const uint8_t> bytes;
};
struct DGifCloser {
void operator()(GifFileType* const ptr) const { DGifCloseFile(ptr, nullptr); }
};
using GifUniquePtr = std::unique_ptr<GifFileType, DGifCloser>;
struct PackedRgba {
uint8_t r, g, b, a;
};
struct PackedRgb {
uint8_t r, g, b;
};
void ensure_have_alpha(PackedFrame* frame) {
if (!frame->extra_channels.empty()) return;
const JxlPixelFormat alpha_format{
/*num_channels=*/1u,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0,
};
frame->extra_channels.emplace_back(frame->color.xsize, frame->color.ysize,
alpha_format);
// We need to set opaque-by-default.
std::fill_n(static_cast<uint8_t*>(frame->extra_channels[0].pixels()),
frame->color.xsize * frame->color.ysize, 255u);
}
} // namespace
#endif
bool CanDecodeGIF() {
#if JPEGXL_ENABLE_GIF
return true;
#else
return false;
#endif
}
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_GIF
int error = GIF_OK;
ReadState state = {bytes};
const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
int n) {
ReadState* const state = reinterpret_cast<ReadState*>(gif->UserData);
// giflib API requires the input size `n` to be signed int.
if (static_cast<size_t>(n) > state->bytes.size()) {
n = state->bytes.size();
}
memcpy(bytes, state->bytes.data(), n);
state->bytes.remove_prefix(n);
return n;
};
GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error));
if (gif == nullptr) {
if (error == D_GIF_ERR_NOT_GIF_FILE) {
// Not an error.
return false;
} else {
return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(error));
}
}
error = DGifSlurp(gif.get());
if (error != GIF_OK) {
return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(gif->Error));
}
msan::UnpoisonMemory(gif.get(), sizeof(*gif));
if (gif->SColorMap) {
msan::UnpoisonMemory(gif->SColorMap, sizeof(*gif->SColorMap));
msan::UnpoisonMemory(
gif->SColorMap->Colors,
sizeof(*gif->SColorMap->Colors) * gif->SColorMap->ColorCount);
}
msan::UnpoisonMemory(gif->SavedImages,
sizeof(*gif->SavedImages) * gif->ImageCount);
JXL_RETURN_IF_ERROR(
VerifyDimensions<uint32_t>(constraints, gif->SWidth, gif->SHeight));
uint64_t total_pixel_count =
static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
for (int i = 0; i < gif->ImageCount; ++i) {
const SavedImage& image = gif->SavedImages[i];
uint32_t w = image.ImageDesc.Width;
uint32_t h = image.ImageDesc.Height;
JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(constraints, w, h));
uint64_t pixel_count = static_cast<uint64_t>(w) * h;
if (total_pixel_count + pixel_count < total_pixel_count) {
return JXL_FAILURE("Image too big");
}
total_pixel_count += pixel_count;
if (constraints && (total_pixel_count > constraints->dec_max_pixels)) {
return JXL_FAILURE("Image too big");
}
}
if (!gif->SColorMap) {
for (int i = 0; i < gif->ImageCount; ++i) {
if (!gif->SavedImages[i].ImageDesc.ColorMap) {
return JXL_FAILURE("Missing GIF color map");
}
}
}
if (gif->ImageCount > 1) {
ppf->info.have_animation = true;
// Delays in GIF are specified in 100ths of a second.
ppf->info.animation.tps_numerator = 100;
ppf->info.animation.tps_denominator = 1;
}
ppf->frames.clear();
ppf->frames.reserve(gif->ImageCount);
ppf->info.xsize = gif->SWidth;
ppf->info.ysize = gif->SHeight;
ppf->info.bits_per_sample = 8;
ppf->info.exponent_bits_per_sample = 0;
// alpha_bits is later set to 8 if we find a frame with transparent pixels.
ppf->info.alpha_bits = 0;
ppf->info.alpha_exponent_bits = 0;
JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
/*is_gray=*/false, ppf));
ppf->info.num_color_channels = 3;
// Pixel format for the 'canvas' onto which we paint
// the (potentially individually cropped) GIF frames
// of an animation.
const JxlPixelFormat canvas_format{
/*num_channels=*/4u,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0,
};
// Pixel format for the JXL PackedFrame that goes into the
// PackedPixelFile. Here, we use 3 color channels, and provide
// the alpha channel as an extra_channel wherever it is used.
const JxlPixelFormat packed_frame_format{
/*num_channels=*/3u,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0,
};
GifColorType background_color;
if (gif->SColorMap == nullptr ||
gif->SBackGroundColor >= gif->SColorMap->ColorCount) {
background_color = {0, 0, 0};
} else {
background_color = gif->SColorMap->Colors[gif->SBackGroundColor];
}
const PackedRgba background_rgba{background_color.Red, background_color.Green,
background_color.Blue, 0};
PackedFrame canvas(gif->SWidth, gif->SHeight, canvas_format);
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
canvas.color.xsize * canvas.color.ysize, background_rgba);
Rect canvas_rect{0, 0, canvas.color.xsize, canvas.color.ysize};
Rect previous_rect_if_restore_to_background;
bool replace = true;
bool last_base_was_none = true;
for (int i = 0; i < gif->ImageCount; ++i) {
const SavedImage& image = gif->SavedImages[i];
msan::UnpoisonMemory(image.RasterBits, sizeof(*image.RasterBits) *
image.ImageDesc.Width *
image.ImageDesc.Height);
const Rect image_rect(image.ImageDesc.Left, image.ImageDesc.Top,
image.ImageDesc.Width, image.ImageDesc.Height);
Rect total_rect;
if (previous_rect_if_restore_to_background.xsize() != 0 ||
previous_rect_if_restore_to_background.ysize() != 0) {
const size_t xbegin = std::min(
image_rect.x0(), previous_rect_if_restore_to_background.x0());
const size_t ybegin = std::min(
image_rect.y0(), previous_rect_if_restore_to_background.y0());
const size_t xend =
std::max(image_rect.x0() + image_rect.xsize(),
previous_rect_if_restore_to_background.x0() +
previous_rect_if_restore_to_background.xsize());
const size_t yend =
std::max(image_rect.y0() + image_rect.ysize(),
previous_rect_if_restore_to_background.y0() +
previous_rect_if_restore_to_background.ysize());
total_rect = Rect(xbegin, ybegin, xend - xbegin, yend - ybegin);
previous_rect_if_restore_to_background = Rect();
replace = true;
} else {
total_rect = image_rect;
replace = false;
}
if (!image_rect.IsInside(canvas_rect)) {
return JXL_FAILURE("GIF frame extends outside of the canvas");
}
// Allocates the frame buffer.
ppf->frames.emplace_back(total_rect.xsize(), total_rect.ysize(),
packed_frame_format);
PackedFrame* frame = &ppf->frames.back();
// We cannot tell right from the start whether there will be a
// need for an alpha channel. This is discovered only as soon as
// we see a transparent pixel. We hence initialize alpha lazily.
auto set_pixel_alpha = [&frame](size_t x, size_t y, uint8_t a) {
// If we do not have an alpha-channel and a==255 (fully opaque),
// we can skip setting this pixel-value and rely on
// "no alpha channel = no transparency".
if (a == 255 && !frame->extra_channels.empty()) return;
ensure_have_alpha(frame);
static_cast<uint8_t*>(
frame->extra_channels[0].pixels())[y * frame->color.xsize + x] = a;
};
const ColorMapObject* const color_map =
image.ImageDesc.ColorMap ? image.ImageDesc.ColorMap : gif->SColorMap;
JXL_CHECK(color_map);
msan::UnpoisonMemory(color_map, sizeof(*color_map));
msan::UnpoisonMemory(color_map->Colors,
sizeof(*color_map->Colors) * color_map->ColorCount);
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(gif.get(), i, &gcb);
msan::UnpoisonMemory(&gcb, sizeof(gcb));
bool is_full_size = total_rect.x0() == 0 && total_rect.y0() == 0 &&
total_rect.xsize() == canvas.color.xsize &&
total_rect.ysize() == canvas.color.ysize;
if (ppf->info.have_animation) {
frame->frame_info.duration = gcb.DelayTime;
frame->frame_info.layer_info.have_crop = static_cast<int>(!is_full_size);
frame->frame_info.layer_info.crop_x0 = total_rect.x0();
frame->frame_info.layer_info.crop_y0 = total_rect.y0();
frame->frame_info.layer_info.xsize = frame->color.xsize;
frame->frame_info.layer_info.ysize = frame->color.ysize;
if (last_base_was_none) {
replace = true;
}
frame->frame_info.layer_info.blend_info.blendmode =
replace ? JXL_BLEND_REPLACE : JXL_BLEND_BLEND;
// We always only reference at most the last frame
frame->frame_info.layer_info.blend_info.source =
last_base_was_none ? 0u : 1u;
frame->frame_info.layer_info.blend_info.clamp = 1;
frame->frame_info.layer_info.blend_info.alpha = 0;
// TODO(veluca): this could in principle be implemented.
if (last_base_was_none &&
(total_rect.x0() != 0 || total_rect.y0() != 0 ||
total_rect.xsize() != canvas.color.xsize ||
total_rect.ysize() != canvas.color.ysize || !replace)) {
return JXL_FAILURE(
"GIF with dispose-to-0 is not supported for non-full or "
"blended frames");
}
switch (gcb.DisposalMode) {
case DISPOSE_DO_NOT:
case DISPOSE_BACKGROUND:
frame->frame_info.layer_info.save_as_reference = 1u;
last_base_was_none = false;
break;
case DISPOSE_PREVIOUS:
frame->frame_info.layer_info.save_as_reference = 0u;
break;
default:
frame->frame_info.layer_info.save_as_reference = 0u;
last_base_was_none = true;
}
}
// Update the canvas by creating a copy first.
PackedImage new_canvas_image(canvas.color.xsize, canvas.color.ysize,
canvas.color.format);
memcpy(new_canvas_image.pixels(), canvas.color.pixels(),
new_canvas_image.pixels_size);
for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
// Assumes format.align == 0. row points to the beginning of the y row in
// the image_rect.
PackedRgba* row = static_cast<PackedRgba*>(new_canvas_image.pixels()) +
(y + image_rect.y0()) * new_canvas_image.xsize +
image_rect.x0();
for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
const GifByteType byte = image.RasterBits[byte_index];
if (byte >= color_map->ColorCount) {
return JXL_FAILURE("GIF color is out of bounds");
}
if (byte == gcb.TransparentColor) continue;
GifColorType color = color_map->Colors[byte];
row[x].r = color.Red;
row[x].g = color.Green;
row[x].b = color.Blue;
row[x].a = 255;
}
}
const PackedImage& sub_frame_image = frame->color;
if (replace) {
// Copy from the new canvas image to the subframe
for (size_t y = 0; y < total_rect.ysize(); ++y) {
const PackedRgba* row_in =
static_cast<const PackedRgba*>(new_canvas_image.pixels()) +
(y + total_rect.y0()) * new_canvas_image.xsize + total_rect.x0();
PackedRgb* row_out = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
y * sub_frame_image.xsize;
for (size_t x = 0; x < sub_frame_image.xsize; ++x) {
row_out[x].r = row_in[x].r;
row_out[x].g = row_in[x].g;
row_out[x].b = row_in[x].b;
set_pixel_alpha(x, y, row_in[x].a);
}
}
} else {
for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
// Assumes format.align == 0
PackedRgb* row = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
y * sub_frame_image.xsize;
for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
const GifByteType byte = image.RasterBits[byte_index];
if (byte > color_map->ColorCount) {
return JXL_FAILURE("GIF color is out of bounds");
}
if (byte == gcb.TransparentColor) {
row[x].r = 0;
row[x].g = 0;
row[x].b = 0;
set_pixel_alpha(x, y, 0);
continue;
}
GifColorType color = color_map->Colors[byte];
row[x].r = color.Red;
row[x].g = color.Green;
row[x].b = color.Blue;
set_pixel_alpha(x, y, 255);
}
}
}
if (!frame->extra_channels.empty()) {
ppf->info.alpha_bits = 8;
}
switch (gcb.DisposalMode) {
case DISPOSE_DO_NOT:
canvas.color = std::move(new_canvas_image);
break;
case DISPOSE_BACKGROUND:
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
canvas.color.xsize * canvas.color.ysize, background_rgba);
previous_rect_if_restore_to_background = image_rect;
break;
case DISPOSE_PREVIOUS:
break;
case DISPOSAL_UNSPECIFIED:
default:
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
canvas.color.xsize * canvas.color.ysize, background_rgba);
}
}
// Finally, if any frame has an alpha-channel, every frame will need
// to have an alpha-channel.
bool seen_alpha = false;
for (const PackedFrame& frame : ppf->frames) {
if (!frame.extra_channels.empty()) {
seen_alpha = true;
break;
}
}
if (seen_alpha) {
for (PackedFrame& frame : ppf->frames) {
ensure_have_alpha(&frame);
}
}
return true;
#else
return false;
#endif
}
} // namespace extras
} // namespace jxl

View File

@ -0,0 +1,35 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_DEC_GIF_H_
#define LIB_EXTRAS_DEC_GIF_H_
// Decodes GIF images in memory.
#include <stdint.h>
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
namespace jxl {
struct SizeConstraints;
namespace extras {
bool CanDecodeGIF();
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_DEC_GIF_H_

Some files were not shown because too many files have changed in this diff Show More