Skip to content

Commit f8570a2

Browse files
authored
Merge pull request #9 from link-u/fix/odd-dimensions-and-color-matrix
Handle images with odd dimensions and fix color matrix, and add CI to check decoding functions
2 parents f73ed4d + a21ca87 commit f8570a2

File tree

7 files changed

+417
-26
lines changed

7 files changed

+417
-26
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Check the decoded images.
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
runs-on: macos-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
- name: Build CLI tool
11+
shell: bash
12+
run: |
13+
set -ex
14+
cd Example
15+
pod install --repo-update
16+
xcrun xcodebuild \
17+
-workspace SDWebImageAVIFCoder.xcworkspace \
18+
-scheme "SDWebImageAVIFCoder_Example CLI" \
19+
-archivePath ./CLI \
20+
archive
21+
- name: Clone test images
22+
shell: bash
23+
run: |
24+
set -ex
25+
git clone https://github.com/link-u/avif-sample-images.git
26+
- name: Decode all AVIF images
27+
shell: bash
28+
run: |
29+
set -ex
30+
cd avif-sample-images
31+
mkdir decoded
32+
CMD="../Example/CLI.xcarchive/Products/usr/local/bin/SDWebImageAVIFCoder_Example CLI"
33+
for file in $(find . -name \*.avif); do
34+
file=$(basename ${file})
35+
"${CMD}" "${file}" "./decoded/${file}.png"
36+
done
37+
- name: Install imagemagick to compare images.
38+
shell: bash
39+
run: brew install imagemagick
40+
- name: Compare images
41+
shell: bash
42+
run: |
43+
set -ex
44+
cd avif-sample-images
45+
for file in $(find . -name \*.avif); do
46+
file=$(basename ${file})
47+
if (echo ${file} | grep "\(monochrome\|crop\|rotate\|mirror\)"); then
48+
# FIXME(ledyba-z): Check them.
49+
echo "Ignore: ${file}"
50+
continue
51+
else
52+
orig=$(cat Makefile | grep "^${file}" | sed "s/^${file}: \(.*\)$/\1/")
53+
score=$(compare -metric PSNR "${orig}" "decoded/${file}.png" NULL: 2>&1 || true)
54+
echo " * ${file}: ${score}"
55+
if test $(echo "${score} >= 35.0" | bc -l) -eq 0; then
56+
exit -1
57+
fi
58+
fi
59+
done

Example/Podfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ target 'SDWebImageAVIFCoder_Example macOS' do
1717
pod 'SDWebImageAVIFCoder', :path => '../'
1818
pod 'libavif', :subspecs => ['librav1e', 'libdav1d']
1919
end
20+
21+
target 'SDWebImageAVIFCoder_Example CLI' do
22+
platform :osx, '10.10'
23+
pod 'SDWebImageAVIFCoder', :path => '../'
24+
pod 'libavif', :subspecs => ['librav1e', 'libdav1d']
25+
end

Example/SDWebImageAVIFCoder.xcodeproj/project.pbxproj

Lines changed: 187 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1130"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "6D37313D23E88F6B007F654B"
18+
BuildableName = "SDWebImageAVIFCoder_Example CLI"
19+
BlueprintName = "SDWebImageAVIFCoder_Example CLI"
20+
ReferencedContainer = "container:SDWebImageAVIFCoder.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
<BuildableProductRunnable
44+
runnableDebuggingMode = "0">
45+
<BuildableReference
46+
BuildableIdentifier = "primary"
47+
BlueprintIdentifier = "6D37313D23E88F6B007F654B"
48+
BuildableName = "SDWebImageAVIFCoder_Example CLI"
49+
BlueprintName = "SDWebImageAVIFCoder_Example CLI"
50+
ReferencedContainer = "container:SDWebImageAVIFCoder.xcodeproj">
51+
</BuildableReference>
52+
</BuildableProductRunnable>
53+
</LaunchAction>
54+
<ProfileAction
55+
buildConfiguration = "Release"
56+
shouldUseLaunchSchemeArgsEnv = "YES"
57+
savedToolIdentifier = ""
58+
useCustomWorkingDirectory = "NO"
59+
debugDocumentVersioning = "YES">
60+
<BuildableProductRunnable
61+
runnableDebuggingMode = "0">
62+
<BuildableReference
63+
BuildableIdentifier = "primary"
64+
BlueprintIdentifier = "6D37313D23E88F6B007F654B"
65+
BuildableName = "SDWebImageAVIFCoder_Example CLI"
66+
BlueprintName = "SDWebImageAVIFCoder_Example CLI"
67+
ReferencedContainer = "container:SDWebImageAVIFCoder.xcodeproj">
68+
</BuildableReference>
69+
</BuildableProductRunnable>
70+
</ProfileAction>
71+
<AnalyzeAction
72+
buildConfiguration = "Debug">
73+
</AnalyzeAction>
74+
<ArchiveAction
75+
buildConfiguration = "Release"
76+
revealArchiveInOrganizer = "YES">
77+
</ArchiveAction>
78+
</Scheme>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// main.m
3+
// SDWebImageAVIFCoder_Example CLI
4+
//
5+
// Created by psi on 2020/02/04.
6+
// Copyright © 2020 [email protected]. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
#import <SDWebImage/SDWebImage.h>
11+
#import <SDWebImageAVIFCoder/SDImageAVIFCoder.h>
12+
13+
int main(int argc, const char * argv[]) {
14+
if(argc != 3) {
15+
fprintf(stderr, "usage: %s <inputPath> <outputPath>\n", argv[0]);
16+
return -1;
17+
}
18+
@autoreleasepool {
19+
NSString* inputPath = [NSString stringWithUTF8String: argv[1]];
20+
NSString* outputPath = [NSString stringWithUTF8String: argv[2]];
21+
NSData* data = [[NSData alloc] initWithContentsOfFile: inputPath];
22+
SDImageAVIFCoder* const coder = [SDImageAVIFCoder sharedCoder];
23+
UIImage* img = [coder decodedImageWithData: data options:nil];
24+
25+
CGImageRef cgRef = [img CGImageForProposedRect:nil context:nil hints:nil];
26+
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
27+
[newRep setSize:[img size]]; // if you want the same resolution
28+
NSDictionary *prop = [[NSDictionary alloc] init];
29+
NSData* pngData = [newRep representationUsingType:NSBitmapImageFileTypePNG properties: prop];
30+
[pngData writeToFile:outputPath atomically:YES];
31+
}
32+
return 0;
33+
}

SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ static void SetupConversionInfo(avifImage * avif,
2121

2222
// Setup Matrix
2323
matrix->Yp = 1.0f;
24-
matrix->Cr_R = 2.0f * (1.0f - state->kr);
25-
matrix->Cb_B = 2.0f * (1.0f - state->kb);
26-
matrix->Cb_G = -2.0f * (1.0f - state->kr) * state->kr / state->kg;
27-
matrix->Cr_G = -2.0f * (1.0f - state->kb) * state->kb / state->kg;
28-
24+
25+
matrix->Cb_B = 2.0f * (1.0f - state->kb);
26+
matrix->Cb_G = -2.0f * (1.0f - state->kb) * state->kb / state->kg;
27+
28+
matrix->Cr_R = 2.0f * (1.0f - state->kr);
29+
matrix->Cr_G = -2.0f * (1.0f - state->kr) * state->kr / state->kg;
30+
2931
// Setup Pixel Range
3032
switch (avif->depth) {
3133
case 8:
@@ -141,8 +143,8 @@ static void ConvertAvifImagePlanar8ToRGB8(avifImage * avif, uint8_t * outPixels)
141143
vImage_Buffer origCb = {
142144
.data = avif->yuvPlanes[AVIF_CHAN_U],
143145
.rowBytes = avif->yuvRowBytes[AVIF_CHAN_U],
144-
.width = avif->width >> state.formatInfo.chromaShiftX,
145-
.height = avif->height >> state.formatInfo.chromaShiftY,
146+
.width = (avif->width+state.formatInfo.chromaShiftX) >> state.formatInfo.chromaShiftX,
147+
.height = (avif->height+state.formatInfo.chromaShiftY) >> state.formatInfo.chromaShiftY,
146148
};
147149

148150
if(!origCb.data) { // allocate dummy data to convert monochrome images.
@@ -159,8 +161,8 @@ static void ConvertAvifImagePlanar8ToRGB8(avifImage * avif, uint8_t * outPixels)
159161
vImage_Buffer origCr = {
160162
.data = avif->yuvPlanes[AVIF_CHAN_V],
161163
.rowBytes = avif->yuvRowBytes[AVIF_CHAN_V],
162-
.width = avif->width >> state.formatInfo.chromaShiftX,
163-
.height = avif->height >> state.formatInfo.chromaShiftY,
164+
.width = (avif->width+state.formatInfo.chromaShiftX) >> state.formatInfo.chromaShiftX,
165+
.height = (avif->height+state.formatInfo.chromaShiftY) >> state.formatInfo.chromaShiftY,
164166
};
165167
if(!origCr.data) { // allocate dummy data to convert monochrome images.
166168
dummyCr = calloc(origCr.width, sizeof(uint8_t));
@@ -282,23 +284,38 @@ static void ConvertAvifImagePlanar8ToRGB8(avifImage * avif, uint8_t * outPixels)
282284
return;
283285
}
284286

287+
((uint8_t*)origY.data)[origY.rowBytes * (origY.height-1) + origY.width ] = 255;
288+
const vImagePixelCount alignedWidth = (origY.width+1) & (~1);
285289
vImage_Buffer tmpY1 = {
286-
.data = calloc(origY.width/2 * origY.height, sizeof(uint8_t)),
287-
.width = origY.width/2,
290+
.data = calloc(alignedWidth/2 * origY.height, sizeof(uint8_t)),
291+
.width = alignedWidth/2,
288292
.height = origY.height,
289-
.rowBytes = origY.width/2 * sizeof(uint8_t),
293+
.rowBytes = alignedWidth/2 * sizeof(uint8_t),
290294
};
291295
if(!tmpY1.data) {
292296
free(argbPixels);
293297
free(dummyCb);
294298
free(dummyCr);
295299
return;
296300
}
301+
err = vImageConvert_ChunkyToPlanar8((const void*[]){origY.data},
302+
(const vImage_Buffer*[]){&tmpY1},
303+
1 /* channelCount */, 2 /* src srcStrideBytes */,
304+
alignedWidth/2, origY.height,
305+
origY.rowBytes, kvImageNoFlags);
306+
if(err != kvImageNoError) {
307+
NSLog(@"Failed to separate first Y channel: %ld", err);
308+
free(argbPixels);
309+
free(dummyCb);
310+
free(dummyCr);
311+
free(tmpY1.data);
312+
return;
313+
}
297314
vImage_Buffer tmpY2 = {
298-
.data = calloc(origY.width/2 * origY.height, sizeof(uint8_t)),
299-
.width = origY.width/2,
315+
.data = calloc(alignedWidth/2 * origY.height, sizeof(uint8_t)),
316+
.width = alignedWidth/2,
300317
.height = origY.height,
301-
.rowBytes = origY.width/2 * sizeof(uint8_t),
318+
.rowBytes = alignedWidth/2 * sizeof(uint8_t),
302319
};
303320
if(!tmpY2.data) {
304321
free(argbPixels);
@@ -307,13 +324,15 @@ static void ConvertAvifImagePlanar8ToRGB8(avifImage * avif, uint8_t * outPixels)
307324
free(tmpY1.data);
308325
return;
309326
}
310-
err= vImageConvert_ChunkyToPlanar8((const void*[]){origY.data, origY.data+1},
311-
(const vImage_Buffer*[]){&tmpY1, &tmpY2},
312-
2 /* channelCount */,2 /* src srcStrideBytes */,
327+
tmpY2.width = origY.width/2;
328+
err = vImageConvert_ChunkyToPlanar8((const void*[]){origY.data + 1},
329+
(const vImage_Buffer*[]){&tmpY2},
330+
1 /* channelCount */, 2 /* src srcStrideBytes */,
313331
origY.width/2, origY.height,
314332
origY.rowBytes, kvImageNoFlags);
333+
tmpY2.width = alignedWidth/2;
315334
if(err != kvImageNoError) {
316-
NSLog(@"Failed to separate Y channel: %ld", err);
335+
NSLog(@"Failed to separate second Y channel: %ld", err);
317336
free(argbPixels);
318337
free(dummyCb);
319338
free(dummyCr);
@@ -322,10 +341,10 @@ static void ConvertAvifImagePlanar8ToRGB8(avifImage * avif, uint8_t * outPixels)
322341
return;
323342
}
324343
vImage_Buffer tmpBuffer = {
325-
.data = calloc(avif->width * avif->height * 2, sizeof(uint8_t)),
326-
.width = avif->width/2,
344+
.data = calloc(alignedWidth * avif->height * 2, sizeof(uint8_t)),
345+
.width = alignedWidth/2,
327346
.height = avif->height,
328-
.rowBytes = avif->width / 2 * 4 * sizeof(uint8_t),
347+
.rowBytes = alignedWidth / 2 * 4 * sizeof(uint8_t),
329348
};
330349
if(!tmpBuffer.data) {
331350
free(argbPixels);
@@ -437,8 +456,8 @@ static void ConvertAvifImagePlanar16ToRGB16U(avifImage * avif, uint8_t * outPixe
437456
vImage_Buffer origCb = {
438457
.data = avif->yuvPlanes[AVIF_CHAN_U],
439458
.rowBytes = avif->yuvRowBytes[AVIF_CHAN_U],
440-
.width = avif->width >> state.formatInfo.chromaShiftX,
441-
.height = avif->height >> state.formatInfo.chromaShiftY,
459+
.width = (avif->width+state.formatInfo.chromaShiftX) >> state.formatInfo.chromaShiftX,
460+
.height = (avif->height+state.formatInfo.chromaShiftY) >> state.formatInfo.chromaShiftY,
442461
};
443462

444463
if(!origCb.data) { // allocate dummy data to convert monochrome images.
@@ -465,8 +484,8 @@ static void ConvertAvifImagePlanar16ToRGB16U(avifImage * avif, uint8_t * outPixe
465484
vImage_Buffer origCr = {
466485
.data = avif->yuvPlanes[AVIF_CHAN_V],
467486
.rowBytes = avif->yuvRowBytes[AVIF_CHAN_V],
468-
.width = avif->width >> state.formatInfo.chromaShiftX,
469-
.height = avif->height >> state.formatInfo.chromaShiftY,
487+
.width = (avif->width+state.formatInfo.chromaShiftX) >> state.formatInfo.chromaShiftX,
488+
.height = (avif->height+state.formatInfo.chromaShiftY) >> state.formatInfo.chromaShiftY,
470489
};
471490

472491
if(!origCr.data) { // allocate dummy data to convert monochrome images.
@@ -823,6 +842,10 @@ - (nullable CGImageRef)sd_createAVIFImageWithData:(nonnull NSData *)data CF_RETU
823842
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, dest, rowBytes * height, FreeImageData);
824843
CGBitmapInfo bitmapInfo = usesU16 ? kCGBitmapByteOrder16Host : kCGBitmapByteOrderDefault;
825844
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone;
845+
// FIXME: (ledyba-z): Set appropriate color space.
846+
// Currently, there is no way to get MatrixCoefficients, TransferCharacteristics and ColourPrimaries values
847+
// in Sequence Header OBU.
848+
// https://github.com/AOMediaCodec/libavif/blob/7d36984b2994210b/include/avif/avif.h#L149-L236
826849
CGColorSpaceRef colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB];
827850
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
828851
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, rowBytes, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

0 commit comments

Comments
 (0)