/src/libavif/apps/shared/avifutil.c
Line | Count | Source |
1 | | // Copyright 2019 Joe Drago. All rights reserved. |
2 | | // SPDX-License-Identifier: BSD-2-Clause |
3 | | |
4 | | #include "avifutil.h" |
5 | | |
6 | | #include <assert.h> |
7 | | #include <ctype.h> |
8 | | #include <stdio.h> |
9 | | #include <stdlib.h> |
10 | | #include <string.h> |
11 | | |
12 | | #include "avifjpeg.h" |
13 | | #include "avifpng.h" |
14 | | #include "y4m.h" |
15 | | |
16 | | char * avifFileFormatToString(avifAppFileFormat format) |
17 | 0 | { |
18 | 0 | switch (format) { |
19 | 0 | case AVIF_APP_FILE_FORMAT_UNKNOWN: |
20 | 0 | return "unknown"; |
21 | 0 | case AVIF_APP_FILE_FORMAT_AVIF: |
22 | 0 | return "AVIF"; |
23 | 0 | case AVIF_APP_FILE_FORMAT_JPEG: |
24 | 0 | return "JPEG"; |
25 | 0 | case AVIF_APP_FILE_FORMAT_PNG: |
26 | 0 | return "PNG"; |
27 | 0 | case AVIF_APP_FILE_FORMAT_Y4M: |
28 | 0 | return "Y4M"; |
29 | 0 | } |
30 | 0 | return "unknown"; |
31 | 0 | } |
32 | | |
33 | | // |a| and |b| hold int32_t values. The int64_t type is used so that we can negate INT32_MIN without |
34 | | // overflowing int32_t. |
35 | | static int64_t calcGCD(int64_t a, int64_t b) |
36 | 0 | { |
37 | 0 | if (a < 0) { |
38 | 0 | a *= -1; |
39 | 0 | } |
40 | 0 | if (b < 0) { |
41 | 0 | b *= -1; |
42 | 0 | } |
43 | 0 | while (b != 0) { |
44 | 0 | int64_t r = a % b; |
45 | 0 | a = b; |
46 | 0 | b = r; |
47 | 0 | } |
48 | 0 | return a; |
49 | 0 | } |
50 | | |
51 | | static void printClapFraction(const char * name, int32_t n, int32_t d) |
52 | 0 | { |
53 | 0 | printf("%s: %d/%d", name, n, d); |
54 | 0 | if (d != 0) { |
55 | 0 | int64_t gcd = calcGCD(n, d); |
56 | 0 | if (gcd > 1) { |
57 | 0 | int32_t rn = (int32_t)(n / gcd); |
58 | 0 | int32_t rd = (int32_t)(d / gcd); |
59 | 0 | printf(" (%d/%d)", rn, rd); |
60 | 0 | } |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | | static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent, avifProgressiveState progressiveState) |
65 | 0 | { |
66 | 0 | uint32_t width = avif->width; |
67 | 0 | uint32_t height = avif->height; |
68 | 0 | if (gridCols && gridRows) { |
69 | 0 | width *= gridCols; |
70 | 0 | height *= gridRows; |
71 | 0 | } |
72 | 0 | printf(" * Resolution : %ux%u\n", width, height); |
73 | 0 | printf(" * Bit Depth : %u\n", avif->depth); |
74 | 0 | printf(" * Format : %s\n", avifPixelFormatToString(avif->yuvFormat)); |
75 | 0 | if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { |
76 | 0 | printf(" * Chroma Sam. Pos: %u\n", avif->yuvChromaSamplePosition); |
77 | 0 | } |
78 | 0 | printf(" * Alpha : %s\n", alphaPresent ? (avif->alphaPremultiplied ? "Premultiplied" : "Not premultiplied") : "Absent"); |
79 | 0 | printf(" * Range : %s\n", (avif->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited"); |
80 | |
|
81 | 0 | printf(" * Color Primaries: %u\n", avif->colorPrimaries); |
82 | 0 | printf(" * Transfer Char. : %u\n", avif->transferCharacteristics); |
83 | 0 | printf(" * Matrix Coeffs. : %u\n", avif->matrixCoefficients); |
84 | |
|
85 | 0 | if (avif->icc.size != 0) { |
86 | 0 | printf(" * ICC Profile : Present (%" AVIF_FMT_ZU " bytes)\n", avif->icc.size); |
87 | 0 | } else { |
88 | 0 | printf(" * ICC Profile : Absent\n"); |
89 | 0 | } |
90 | 0 | if (avif->xmp.size != 0) { |
91 | 0 | printf(" * XMP Metadata : Present (%" AVIF_FMT_ZU " bytes)\n", avif->xmp.size); |
92 | 0 | } else { |
93 | 0 | printf(" * XMP Metadata : Absent\n"); |
94 | 0 | } |
95 | 0 | if (avif->exif.size != 0) { |
96 | 0 | printf(" * Exif Metadata : Present (%" AVIF_FMT_ZU " bytes)\n", avif->exif.size); |
97 | 0 | } else { |
98 | 0 | printf(" * Exif Metadata : Absent\n"); |
99 | 0 | } |
100 | |
|
101 | 0 | if (avif->transformFlags == AVIF_TRANSFORM_NONE) { |
102 | 0 | printf(" * Transformations: None\n"); |
103 | 0 | } else { |
104 | 0 | printf(" * Transformations:\n"); |
105 | |
|
106 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_PASP) { |
107 | 0 | printf(" * pasp (Aspect Ratio) : %d/%d\n", (int)avif->pasp.hSpacing, (int)avif->pasp.vSpacing); |
108 | 0 | } |
109 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { |
110 | 0 | printf(" * clap (Clean Aperture): "); |
111 | 0 | printClapFraction("W", (int32_t)avif->clap.widthN, (int32_t)avif->clap.widthD); |
112 | 0 | printf(", "); |
113 | 0 | printClapFraction("H", (int32_t)avif->clap.heightN, (int32_t)avif->clap.heightD); |
114 | 0 | printf(", "); |
115 | 0 | printClapFraction("hOff", (int32_t)avif->clap.horizOffN, (int32_t)avif->clap.horizOffD); |
116 | 0 | printf(", "); |
117 | 0 | printClapFraction("vOff", (int32_t)avif->clap.vertOffN, (int32_t)avif->clap.vertOffD); |
118 | 0 | printf("\n"); |
119 | |
|
120 | 0 | avifCropRect cropRect; |
121 | 0 | avifDiagnostics diag; |
122 | 0 | avifDiagnosticsClearError(&diag); |
123 | 0 | avifBool validClap = avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag); |
124 | 0 | if (validClap) { |
125 | 0 | printf(" * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d%s\n", |
126 | 0 | cropRect.x, |
127 | 0 | cropRect.y, |
128 | 0 | cropRect.width, |
129 | 0 | cropRect.height, |
130 | 0 | avifCropRectRequiresUpsampling(&cropRect, avif->yuvFormat) ? " (upsample before cropping)" : ""); |
131 | 0 | } else { |
132 | 0 | printf(" * Invalid: %s\n", diag.error); |
133 | 0 | } |
134 | 0 | } |
135 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_IROT) { |
136 | 0 | printf(" * irot (Rotation) : %u\n", avif->irot.angle); |
137 | 0 | } |
138 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_IMIR) { |
139 | 0 | printf(" * imir (Mirror) : %u (%s)\n", avif->imir.axis, (avif->imir.axis == 0) ? "top-to-bottom" : "left-to-right"); |
140 | 0 | } |
141 | 0 | } |
142 | 0 | printf(" * Progressive : %s\n", avifProgressiveStateToString(progressiveState)); |
143 | 0 | if (avif->clli.maxCLL > 0 || avif->clli.maxPALL > 0) { |
144 | 0 | printf(" * CLLI : %hu, %hu\n", avif->clli.maxCLL, avif->clli.maxPALL); |
145 | 0 | } |
146 | |
|
147 | 0 | printf(" * Gain map : "); |
148 | 0 | avifImage * gainMapImage = avif->gainMap ? avif->gainMap->image : NULL; |
149 | 0 | if (gainMapImage != NULL) { |
150 | 0 | uint32_t gainMapWidth = gainMapImage->width; |
151 | 0 | uint32_t gainMapHeight = gainMapImage->height; |
152 | 0 | if (gridCols && gridRows) { |
153 | 0 | gainMapWidth *= gridCols; |
154 | 0 | gainMapHeight *= gridRows; |
155 | 0 | } |
156 | 0 | printf("%ux%u pixels, %u bit, %s, %s Range, Matrix Coeffs. %u, Base Headroom %.2f (%s), Alternate Headroom %.2f (%s)\n", |
157 | 0 | gainMapWidth, |
158 | 0 | gainMapHeight, |
159 | 0 | gainMapImage->depth, |
160 | 0 | avifPixelFormatToString(gainMapImage->yuvFormat), |
161 | 0 | (gainMapImage->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited", |
162 | 0 | gainMapImage->matrixCoefficients, |
163 | 0 | avif->gainMap->baseHdrHeadroom.d == 0 ? 0 |
164 | 0 | : (double)avif->gainMap->baseHdrHeadroom.n / avif->gainMap->baseHdrHeadroom.d, |
165 | 0 | (avif->gainMap->baseHdrHeadroom.n == 0) ? "SDR" : "HDR", |
166 | 0 | avif->gainMap->alternateHdrHeadroom.d == 0 |
167 | 0 | ? 0 |
168 | 0 | : (double)avif->gainMap->alternateHdrHeadroom.n / avif->gainMap->alternateHdrHeadroom.d, |
169 | 0 | (avif->gainMap->alternateHdrHeadroom.n == 0) ? "SDR" : "HDR"); |
170 | 0 | printf(" * Alternate image:\n"); |
171 | 0 | printf(" * Color Primaries: %u\n", avif->gainMap->altColorPrimaries); |
172 | 0 | printf(" * Transfer Char. : %u\n", avif->gainMap->altTransferCharacteristics); |
173 | 0 | printf(" * Matrix Coeffs. : %u\n", avif->gainMap->altMatrixCoefficients); |
174 | 0 | if (avif->gainMap->altICC.size != 0) { |
175 | 0 | printf(" * ICC Profile : Present (%" AVIF_FMT_ZU " bytes)\n", avif->gainMap->altICC.size); |
176 | 0 | } else { |
177 | 0 | printf(" * ICC Profile : Absent\n"); |
178 | 0 | } |
179 | 0 | if (avif->gainMap->altDepth) { |
180 | 0 | printf(" * Bit Depth : %u\n", avif->gainMap->altDepth); |
181 | 0 | } |
182 | 0 | if (avif->gainMap->altPlaneCount) { |
183 | 0 | printf(" * Planes : %u\n", avif->gainMap->altPlaneCount); |
184 | 0 | } |
185 | 0 | if (avif->gainMap->altCLLI.maxCLL > 0 || avif->gainMap->altCLLI.maxPALL > 0) { |
186 | 0 | printf(" * CLLI : %hu, %hu\n", avif->gainMap->altCLLI.maxCLL, avif->gainMap->altCLLI.maxPALL); |
187 | 0 | } |
188 | 0 | printf("\n"); |
189 | 0 | } else if (avif->gainMap != NULL) { |
190 | 0 | printf("Present (but ignored)\n"); |
191 | 0 | } else { |
192 | 0 | printf("Absent\n"); |
193 | 0 | } |
194 | 0 | } |
195 | | |
196 | | void avifImageDump(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState) |
197 | 0 | { |
198 | 0 | const avifBool alphaPresent = avif->alphaPlane && (avif->alphaRowBytes > 0); |
199 | 0 | avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent, progressiveState); |
200 | 0 | } |
201 | | |
202 | | void avifContainerDump(const avifDecoder * decoder) |
203 | 0 | { |
204 | 0 | avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent, decoder->progressiveState); |
205 | 0 | if (decoder->imageSequenceTrackPresent) { |
206 | 0 | if (decoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) { |
207 | 0 | printf(" * Repeat Count : Infinite\n"); |
208 | 0 | } else if (decoder->repetitionCount == AVIF_REPETITION_COUNT_UNKNOWN) { |
209 | 0 | printf(" * Repeat Count : Unknown\n"); |
210 | 0 | } else { |
211 | 0 | printf(" * Repeat Count : %d\n", decoder->repetitionCount); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | | |
216 | | void avifPrintVersions(void) |
217 | 0 | { |
218 | 0 | char codecVersions[256]; |
219 | 0 | avifCodecVersions(codecVersions); |
220 | 0 | printf("Version: %s (%s)\n", avifVersion(), codecVersions); |
221 | |
|
222 | 0 | unsigned int libyuvVersion = avifLibYUVVersion(); |
223 | 0 | if (libyuvVersion == 0) { |
224 | 0 | printf("libyuv : unavailable\n"); |
225 | 0 | } else { |
226 | 0 | printf("libyuv : available (%u)\n", libyuvVersion); |
227 | 0 | } |
228 | |
|
229 | 0 | printf("\n"); |
230 | 0 | } |
231 | | |
232 | | avifAppFileFormat avifGuessFileFormat(const char * filename) |
233 | 0 | { |
234 | | // Guess from the file header |
235 | 0 | FILE * f = fopen(filename, "rb"); |
236 | 0 | if (f) { |
237 | 0 | uint8_t headerBuffer[144]; |
238 | 0 | size_t bytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), f); |
239 | 0 | fclose(f); |
240 | |
|
241 | 0 | if (bytesRead > 0) { |
242 | | // If the file could be read, use the first bytes to guess the file format. |
243 | 0 | return avifGuessBufferFileFormat(headerBuffer, bytesRead); |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | // If we get here, the file header couldn't be read for some reason. Guess from the extension. |
248 | | |
249 | 0 | const char * fileExt = strrchr(filename, '.'); |
250 | 0 | if (!fileExt) { |
251 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
252 | 0 | } |
253 | 0 | ++fileExt; // skip past the dot |
254 | |
|
255 | 0 | char lowercaseExt[8]; // This only needs to fit up to "jpeg", so this is plenty |
256 | 0 | const size_t fileExtLen = strlen(fileExt); |
257 | 0 | if (fileExtLen >= sizeof(lowercaseExt)) { // >= accounts for NULL terminator |
258 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
259 | 0 | } |
260 | | |
261 | 0 | for (size_t i = 0; i < fileExtLen; ++i) { |
262 | 0 | lowercaseExt[i] = (char)tolower((unsigned char)fileExt[i]); |
263 | 0 | } |
264 | 0 | lowercaseExt[fileExtLen] = 0; |
265 | |
|
266 | 0 | if (!strcmp(lowercaseExt, "avif")) { |
267 | 0 | return AVIF_APP_FILE_FORMAT_AVIF; |
268 | 0 | } else if (!strcmp(lowercaseExt, "y4m")) { |
269 | 0 | return AVIF_APP_FILE_FORMAT_Y4M; |
270 | 0 | } else if (!strcmp(lowercaseExt, "jpg") || !strcmp(lowercaseExt, "jpeg")) { |
271 | 0 | return AVIF_APP_FILE_FORMAT_JPEG; |
272 | 0 | } else if (!strcmp(lowercaseExt, "png")) { |
273 | 0 | return AVIF_APP_FILE_FORMAT_PNG; |
274 | 0 | } |
275 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
276 | 0 | } |
277 | | |
278 | | avifAppFileFormat avifGuessBufferFileFormat(const uint8_t * data, size_t size) |
279 | 1.13k | { |
280 | 1.13k | if (size == 0) { |
281 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
282 | 0 | } |
283 | | |
284 | 1.13k | avifROData header; |
285 | 1.13k | header.data = data; |
286 | 1.13k | header.size = size; |
287 | | |
288 | 1.13k | if (avifPeekCompatibleFileType(&header)) { |
289 | 759 | return AVIF_APP_FILE_FORMAT_AVIF; |
290 | 759 | } |
291 | | |
292 | 374 | static const uint8_t signatureJPEG[2] = { 0xFF, 0xD8 }; |
293 | 374 | static const uint8_t signaturePNG[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; |
294 | 374 | static const uint8_t signatureY4M[9] = { 0x59, 0x55, 0x56, 0x34, 0x4D, 0x50, 0x45, 0x47, 0x32 }; // "YUV4MPEG2" |
295 | 374 | struct avifHeaderSignature |
296 | 374 | { |
297 | 374 | avifAppFileFormat format; |
298 | 374 | const uint8_t * magic; |
299 | 374 | size_t magicSize; |
300 | 374 | } signatures[] = { { AVIF_APP_FILE_FORMAT_JPEG, signatureJPEG, sizeof(signatureJPEG) }, |
301 | 374 | { AVIF_APP_FILE_FORMAT_PNG, signaturePNG, sizeof(signaturePNG) }, |
302 | 374 | { AVIF_APP_FILE_FORMAT_Y4M, signatureY4M, sizeof(signatureY4M) } }; |
303 | 374 | const size_t signaturesCount = sizeof(signatures) / sizeof(signatures[0]); |
304 | | |
305 | 693 | for (size_t signatureIndex = 0; signatureIndex < signaturesCount; ++signatureIndex) { |
306 | 671 | const struct avifHeaderSignature * const signature = &signatures[signatureIndex]; |
307 | 671 | if (header.size < signature->magicSize) { |
308 | 0 | continue; |
309 | 0 | } |
310 | 671 | if (!memcmp(header.data, signature->magic, signature->magicSize)) { |
311 | 352 | return signature->format; |
312 | 352 | } |
313 | 671 | } |
314 | | |
315 | 22 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
316 | 374 | } |
317 | | |
318 | | avifAppFileFormat avifReadImage(const char * filename, |
319 | | avifAppFileFormat inputFormat, |
320 | | avifPixelFormat requestedFormat, |
321 | | int requestedDepth, |
322 | | avifChromaDownsampling chromaDownsampling, |
323 | | avifBool ignoreColorProfile, |
324 | | avifBool ignoreExif, |
325 | | avifBool ignoreXMP, |
326 | | avifBool ignoreAlpha, |
327 | | avifBool ignoreGainMap, |
328 | | uint32_t imageSizeLimit, |
329 | | avifImage * image, |
330 | | uint32_t * outDepth, |
331 | | avifAppSourceTiming * sourceTiming, |
332 | | struct y4mFrameIterator ** frameIter) |
333 | 0 | { |
334 | 0 | if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { |
335 | 0 | inputFormat = avifGuessFileFormat(filename); |
336 | 0 | } |
337 | |
|
338 | 0 | if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) { |
339 | 0 | if (!y4mRead(filename, ignoreAlpha, imageSizeLimit, image, sourceTiming, frameIter)) { |
340 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
341 | 0 | } |
342 | 0 | if (outDepth) { |
343 | 0 | *outDepth = image->depth; |
344 | 0 | } |
345 | 0 | } else if (inputFormat == AVIF_APP_FILE_FORMAT_JPEG) { |
346 | | // imageSizeLimit is also used to limit Exif and XMP metadata here. |
347 | 0 | if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap, imageSizeLimit)) { |
348 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
349 | 0 | } |
350 | 0 | if (outDepth) { |
351 | 0 | *outDepth = 8; |
352 | 0 | } |
353 | 0 | } else if (inputFormat == AVIF_APP_FILE_FORMAT_PNG) { |
354 | 0 | if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreAlpha, imageSizeLimit, outDepth)) { |
355 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
356 | 0 | } |
357 | 0 | } else if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { |
358 | 0 | fprintf(stderr, "Unrecognized file format for input file: %s\n", filename); |
359 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
360 | 0 | } else { |
361 | 0 | fprintf(stderr, "Unsupported file format %s for input file: %s\n", avifFileFormatToString(inputFormat), filename); |
362 | 0 | return AVIF_APP_FILE_FORMAT_UNKNOWN; |
363 | 0 | } |
364 | 0 | return inputFormat; |
365 | 0 | } |
366 | | |
367 | | avifBool avifReadEntireFile(const char * filename, avifRWData * raw) |
368 | 0 | { |
369 | 0 | FILE * f = fopen(filename, "rb"); |
370 | 0 | if (!f) { |
371 | 0 | return AVIF_FALSE; |
372 | 0 | } |
373 | | |
374 | 0 | fseek(f, 0, SEEK_END); |
375 | 0 | long pos = ftell(f); |
376 | 0 | if (pos <= 0) { |
377 | 0 | fclose(f); |
378 | 0 | return AVIF_FALSE; |
379 | 0 | } |
380 | 0 | size_t fileSize = (size_t)pos; |
381 | 0 | fseek(f, 0, SEEK_SET); |
382 | |
|
383 | 0 | if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) { |
384 | 0 | fclose(f); |
385 | 0 | return AVIF_FALSE; |
386 | 0 | } |
387 | 0 | size_t bytesRead = fread(raw->data, 1, fileSize, f); |
388 | 0 | fclose(f); |
389 | |
|
390 | 0 | if (bytesRead != fileSize) { |
391 | 0 | avifRWDataFree(raw); |
392 | 0 | return AVIF_FALSE; |
393 | 0 | } |
394 | 0 | return AVIF_TRUE; |
395 | 0 | } |
396 | | |
397 | | void avifImageFixXMP(avifImage * image) |
398 | 0 | { |
399 | | // Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML |
400 | | // Keeping zero bytes in XMP may lead to issues at encoding or decoding. |
401 | | // For example, the PNG specification forbids null characters in XMP. See avifPNGWrite(). |
402 | | // The XMP Specification Part 3 says "When XMP is encoded as UTF-8, |
403 | | // there are no zero bytes in the XMP packet" for GIF. |
404 | | |
405 | | // Consider a single trailing null character following a non-null character |
406 | | // as a programming error. Leave other null characters as is. |
407 | | // See the discussion at https://github.com/AOMediaCodec/libavif/issues/1333. |
408 | 0 | if (image->xmp.size >= 2 && image->xmp.data[image->xmp.size - 1] == '\0' && image->xmp.data[image->xmp.size - 2] != '\0') { |
409 | 0 | --image->xmp.size; |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | | void avifDumpDiagnostics(const avifDiagnostics * diag) |
414 | 0 | { |
415 | 0 | if (!*diag->error) { |
416 | 0 | return; |
417 | 0 | } |
418 | | |
419 | 0 | printf("Diagnostics:\n"); |
420 | 0 | printf(" * %s\n", diag->error); |
421 | 0 | } |
422 | | |
423 | | // --------------------------------------------------------------------------- |
424 | | // avifQueryCPUCount (separated into OS implementations) |
425 | | |
426 | | #if defined(_WIN32) |
427 | | |
428 | | // Windows |
429 | | |
430 | | #include <windows.h> |
431 | | |
432 | | int avifQueryCPUCount(void) |
433 | | { |
434 | | int numCPU; |
435 | | SYSTEM_INFO sysinfo; |
436 | | GetSystemInfo(&sysinfo); |
437 | | numCPU = sysinfo.dwNumberOfProcessors; |
438 | | return numCPU; |
439 | | } |
440 | | |
441 | | #elif defined(__APPLE__) |
442 | | |
443 | | // Apple |
444 | | |
445 | | #include <sys/sysctl.h> |
446 | | |
447 | | int avifQueryCPUCount(void) |
448 | | { |
449 | | int mib[4]; |
450 | | int numCPU; |
451 | | size_t len = sizeof(numCPU); |
452 | | |
453 | | /* set the mib for hw.ncpu */ |
454 | | mib[0] = CTL_HW; |
455 | | mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU; |
456 | | |
457 | | /* get the number of CPUs from the system */ |
458 | | sysctl(mib, 2, &numCPU, &len, NULL, 0); |
459 | | |
460 | | if (numCPU < 1) { |
461 | | mib[1] = HW_NCPU; |
462 | | sysctl(mib, 2, &numCPU, &len, NULL, 0); |
463 | | if (numCPU < 1) |
464 | | numCPU = 1; |
465 | | } |
466 | | return numCPU; |
467 | | } |
468 | | |
469 | | #elif defined(__EMSCRIPTEN__) |
470 | | |
471 | | // Emscripten |
472 | | |
473 | | int avifQueryCPUCount(void) |
474 | | { |
475 | | return 1; |
476 | | } |
477 | | |
478 | | #else |
479 | | |
480 | | // POSIX |
481 | | |
482 | | #include <unistd.h> |
483 | | |
484 | | int avifQueryCPUCount(void) |
485 | 0 | { |
486 | 0 | int numCPU = (int)sysconf(_SC_NPROCESSORS_ONLN); |
487 | 0 | return (numCPU > 0) ? numCPU : 1; |
488 | 0 | } |
489 | | |
490 | | #endif |
491 | | |
492 | | // Returns the best cell size for a given horizontal or vertical dimension. |
493 | | avifBool avifGetBestCellSize(const char * dimensionStr, uint32_t numPixels, uint32_t numCells, avifBool isSubsampled, uint32_t * cellSize) |
494 | 0 | { |
495 | 0 | assert(numPixels); |
496 | 0 | assert(numCells); |
497 | | |
498 | | // ISO/IEC 23008-12:2017, Section 6.6.2.3.1: |
499 | | // The reconstructed image is formed by tiling the input images into a grid with a column width |
500 | | // (potentially excluding the right-most column) equal to tile_width and a row height (potentially |
501 | | // excluding the bottom-most row) equal to tile_height, without gap or overlap, and then |
502 | | // trimming on the right and the bottom to the indicated output_width and output_height. |
503 | | // The priority could be to use a cell size that is a multiple of 64, but there is not always a valid one, |
504 | | // even though it is recommended by MIAF. Just use ceil(numPixels/numCells) for simplicity and to avoid |
505 | | // as much padding in the right-most and bottom-most cells as possible. |
506 | | // Use uint64_t computation to avoid a potential uint32_t overflow. |
507 | 0 | *cellSize = (uint32_t)(((uint64_t)numPixels + numCells - 1) / numCells); |
508 | | |
509 | | // ISO/IEC 23000-22:2019, Section 7.3.11.4.2: |
510 | | // - the tile_width shall be greater than or equal to 64, and should be a multiple of 64 |
511 | | // - the tile_height shall be greater than or equal to 64, and should be a multiple of 64 |
512 | 0 | if (*cellSize < 64) { |
513 | 0 | *cellSize = 64; |
514 | 0 | if ((uint64_t)(numCells - 1) * *cellSize >= (uint64_t)numPixels) { |
515 | | // Some cells would be entirely off-canvas. |
516 | 0 | fprintf(stderr, "ERROR: There are too many cells %s (%u) to have at least 64 pixels per cell.\n", dimensionStr, numCells); |
517 | 0 | return AVIF_FALSE; |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | | // The maximum AV1 frame size is 65536 pixels inclusive. |
522 | 0 | if (*cellSize > 65536) { |
523 | 0 | fprintf(stderr, "ERROR: Cell size %u is bigger %s than the maximum frame size 65536.\n", *cellSize, dimensionStr); |
524 | 0 | return AVIF_FALSE; |
525 | 0 | } |
526 | | |
527 | | // ISO/IEC 23000-22:2019, Section 7.3.11.4.2: |
528 | | // - when the images are in the 4:2:2 chroma sampling format the horizontal tile offsets and widths, |
529 | | // and the output width, shall be even numbers; |
530 | | // - when the images are in the 4:2:0 chroma sampling format both the horizontal and vertical tile |
531 | | // offsets and widths, and the output width and height, shall be even numbers. |
532 | 0 | if (isSubsampled && (*cellSize & 1)) { |
533 | 0 | ++*cellSize; |
534 | 0 | if ((uint64_t)(numCells - 1) * *cellSize >= (uint64_t)numPixels) { |
535 | | // Some cells would be entirely off-canvas. |
536 | 0 | fprintf(stderr, "ERROR: Odd cell size %u is forbidden on a %s subsampled image.\n", *cellSize - 1, dimensionStr); |
537 | 0 | return AVIF_FALSE; |
538 | 0 | } |
539 | 0 | } |
540 | | |
541 | | // Each pixel is covered by exactly one cell, and each cell contains at least one pixel. |
542 | 0 | assert(((uint64_t)(numCells - 1) * *cellSize < (uint64_t)numPixels) && ((uint64_t)numCells * *cellSize >= (uint64_t)numPixels)); |
543 | 0 | return AVIF_TRUE; |
544 | 0 | } |
545 | | |
546 | | avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, avifImage ** gridCells) |
547 | 0 | { |
548 | 0 | avifBool success = AVIF_FALSE; |
549 | 0 | uint32_t cellWidth, cellHeight; |
550 | 0 | avifPixelFormatInfo formatInfo; |
551 | 0 | avifGetPixelFormatInfo(gridSplitImage->yuvFormat, &formatInfo); |
552 | 0 | const avifBool isSubsampledX = !formatInfo.monochrome && formatInfo.chromaShiftX; |
553 | 0 | const avifBool isSubsampledY = !formatInfo.monochrome && formatInfo.chromaShiftY; |
554 | 0 | if (!avifGetBestCellSize("horizontally", gridSplitImage->width, gridCols, isSubsampledX, &cellWidth) || |
555 | 0 | !avifGetBestCellSize("vertically", gridSplitImage->height, gridRows, isSubsampledY, &cellHeight)) { |
556 | 0 | return AVIF_FALSE; |
557 | 0 | } |
558 | 0 | const avifBool hasGainMap = gridSplitImage->gainMap && gridSplitImage->gainMap->image; |
559 | |
|
560 | 0 | uint32_t createdCells = 0; |
561 | 0 | for (uint32_t gridY = 0; gridY < gridRows; ++gridY) { |
562 | 0 | for (uint32_t gridX = 0; gridX < gridCols; ++gridX) { |
563 | 0 | uint32_t gridIndex = gridX + (gridY * gridCols); |
564 | 0 | avifImage * cellImage = avifImageCreateEmpty(); |
565 | 0 | if (!cellImage) { |
566 | 0 | fprintf(stderr, "ERROR: Cell creation failed: out of memory\n"); |
567 | 0 | goto cleanup; |
568 | 0 | } |
569 | 0 | gridCells[gridIndex] = cellImage; |
570 | 0 | assert(gridIndex == createdCells); |
571 | 0 | createdCells++; |
572 | |
|
573 | 0 | avifCropRect cellRect = { gridX * cellWidth, gridY * cellHeight, cellWidth, cellHeight }; |
574 | 0 | if (cellRect.x + cellRect.width > gridSplitImage->width) { |
575 | 0 | cellRect.width = gridSplitImage->width - cellRect.x; |
576 | 0 | } |
577 | 0 | if (cellRect.y + cellRect.height > gridSplitImage->height) { |
578 | 0 | cellRect.height = gridSplitImage->height - cellRect.y; |
579 | 0 | } |
580 | 0 | const avifResult copyResult = avifImageSetViewRect(cellImage, gridSplitImage, &cellRect); |
581 | 0 | if (copyResult != AVIF_RESULT_OK) { |
582 | 0 | fprintf(stderr, "ERROR: Cell creation failed: %s\n", avifResultToString(copyResult)); |
583 | 0 | goto cleanup; |
584 | 0 | } |
585 | | |
586 | 0 | if (hasGainMap) { |
587 | 0 | cellImage->gainMap = avifGainMapCreate(); |
588 | 0 | if (!cellImage->gainMap) { |
589 | 0 | fprintf(stderr, "ERROR: Gain map creation failed: out of memory\n"); |
590 | 0 | goto cleanup; |
591 | 0 | } |
592 | | // Copy gain map metadata. |
593 | 0 | memcpy(cellImage->gainMap, gridSplitImage->gainMap, sizeof(avifGainMap)); |
594 | 0 | cellImage->gainMap->altICC.data = NULL; // Copied later in this function. |
595 | 0 | cellImage->gainMap->altICC.size = 0; |
596 | 0 | cellImage->gainMap->image = NULL; // Set later in this function. |
597 | 0 | } |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | 0 | if (hasGainMap) { |
602 | 0 | avifImage ** gainMapGridCells = NULL; |
603 | 0 | gainMapGridCells = (avifImage **)calloc(gridCols * gridRows, sizeof(avifImage *)); |
604 | 0 | if (!gainMapGridCells) { |
605 | 0 | fprintf(stderr, "ERROR: Memory allocation failed for gain map grid cells\n"); |
606 | 0 | goto cleanup; |
607 | 0 | } |
608 | 0 | if (!avifImageSplitGrid(gridSplitImage->gainMap->image, gridCols, gridRows, gainMapGridCells)) { |
609 | 0 | free(gainMapGridCells); |
610 | 0 | goto cleanup; |
611 | 0 | } |
612 | | |
613 | 0 | for (uint32_t gridIndex = 0; gridIndex < gridCols * gridRows; ++gridIndex) { |
614 | | // Ownership of the gain map cell is transferred. |
615 | 0 | gridCells[gridIndex]->gainMap->image = gainMapGridCells[gridIndex]; |
616 | 0 | } |
617 | 0 | free(gainMapGridCells); |
618 | 0 | } |
619 | | |
620 | | // Copy over metadata blobs to the first cell since avifImageSetViewRect() does not copy any |
621 | | // properties that require an allocation. |
622 | 0 | avifImage * firstCell = gridCells[0]; |
623 | 0 | if (gridSplitImage->icc.size > 0) { |
624 | 0 | const avifResult result = avifImageSetProfileICC(firstCell, gridSplitImage->icc.data, gridSplitImage->icc.size); |
625 | 0 | if (result != AVIF_RESULT_OK) { |
626 | 0 | fprintf(stderr, "ERROR: Failed to set ICC profile on grid cell: %s\n", avifResultToString(result)); |
627 | 0 | goto cleanup; |
628 | 0 | } |
629 | 0 | } |
630 | 0 | if (gridSplitImage->exif.size > 0) { |
631 | 0 | const avifResult result = avifRWDataSet(&firstCell->exif, gridSplitImage->exif.data, gridSplitImage->exif.size); |
632 | 0 | if (result != AVIF_RESULT_OK) { |
633 | 0 | fprintf(stderr, "ERROR: Failed to set Exif metadata on grid cell: %s\n", avifResultToString(result)); |
634 | 0 | goto cleanup; |
635 | 0 | } |
636 | 0 | } |
637 | 0 | if (gridSplitImage->xmp.size > 0) { |
638 | 0 | const avifResult result = avifImageSetMetadataXMP(firstCell, gridSplitImage->xmp.data, gridSplitImage->xmp.size); |
639 | 0 | if (result != AVIF_RESULT_OK) { |
640 | 0 | fprintf(stderr, "ERROR: Failed to set XMP metadata on grid cell: %s\n", avifResultToString(result)); |
641 | 0 | goto cleanup; |
642 | 0 | } |
643 | 0 | } |
644 | 0 | if (gridSplitImage->gainMap && gridSplitImage->gainMap->image && gridSplitImage->gainMap->altICC.size > 0) { |
645 | 0 | for (uint32_t i = 0; i < gridCols * gridRows; ++i) { |
646 | 0 | avifImage * cellImage = gridCells[i]; |
647 | 0 | const avifResult result = |
648 | 0 | avifRWDataSet(&cellImage->gainMap->altICC, gridSplitImage->gainMap->altICC.data, gridSplitImage->gainMap->altICC.size); |
649 | 0 | if (result != AVIF_RESULT_OK) { |
650 | 0 | fprintf(stderr, "ERROR: Failed to set ICC profile on gain map grid cell: %s\n", avifResultToString(result)); |
651 | 0 | goto cleanup; |
652 | 0 | } |
653 | 0 | } |
654 | 0 | } |
655 | | |
656 | 0 | success = AVIF_TRUE; |
657 | |
|
658 | 0 | cleanup: |
659 | 0 | if (!success) { |
660 | 0 | for (uint32_t i = 0; i < createdCells; ++i) { |
661 | 0 | avifImageDestroy(gridCells[i]); |
662 | 0 | gridCells[i] = NULL; |
663 | 0 | } |
664 | 0 | } |
665 | 0 | return success; |
666 | 0 | } |
667 | | |
668 | | void avifRGBImageSetViewRect(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifCropRect * cropRect) |
669 | 0 | { |
670 | 0 | memset(dstImage, 0, sizeof(avifRGBImage)); |
671 | 0 | dstImage->width = cropRect->width; |
672 | 0 | dstImage->height = cropRect->height; |
673 | 0 | dstImage->depth = srcImage->depth; |
674 | 0 | dstImage->format = srcImage->format; |
675 | 0 | dstImage->alphaPremultiplied = srcImage->alphaPremultiplied; |
676 | 0 | dstImage->isFloat = srcImage->isFloat; |
677 | 0 | const uint32_t bytesPerPixel = avifRGBImagePixelSize(srcImage); |
678 | | // This should not overflow if cropRect is a valid crop of the image. |
679 | 0 | const size_t offset = (size_t)cropRect->y * srcImage->rowBytes + (size_t)cropRect->x * bytesPerPixel; |
680 | 0 | dstImage->pixels = srcImage->pixels + offset; |
681 | 0 | dstImage->rowBytes = srcImage->rowBytes; |
682 | 0 | } |
683 | | |
684 | | // NOTE: this saves the rotated pixels to a different image. Rotating an image in place is possible, but can be non trivial depending on the angle. |
685 | | // A 90° rotation can be implemented as a transposition operation followed by mirroring. |
686 | | // It's the transposition step that is non trivial for non-square images, see https://en.wikipedia.org/wiki/In-place_matrix_transposition |
687 | | avifResult avifRGBImageRotate(avifRGBImage * dstImage, const avifRGBImage * srcImage, const avifImageRotation * rotation) |
688 | 0 | { |
689 | 0 | const uint32_t bytesPerPixel = avifRGBImagePixelSize(srcImage); |
690 | 0 | const uint8_t angle = rotation->angle; |
691 | 0 | const uint32_t newWidth = (angle == 0 || angle == 2) ? srcImage->width : srcImage->height; |
692 | 0 | const uint32_t newHeight = (angle == 0 || angle == 2) ? srcImage->height : srcImage->width; |
693 | 0 | *dstImage = *srcImage; |
694 | 0 | dstImage->width = newWidth; |
695 | 0 | dstImage->height = newHeight; |
696 | 0 | dstImage->pixels = NULL; |
697 | 0 | avifResult result = avifRGBImageAllocatePixels(dstImage); |
698 | 0 | if (result != AVIF_RESULT_OK) { |
699 | 0 | return result; |
700 | 0 | } |
701 | | |
702 | 0 | if (rotation->angle == 0) { |
703 | 0 | const size_t bytesPerRow = (size_t)bytesPerPixel * srcImage->width; |
704 | | // 0 degrees. Just copy the rows as is. |
705 | 0 | for (uint32_t j = 0; j < srcImage->height; ++j) { |
706 | 0 | memcpy(dstImage->pixels + ((size_t)j * dstImage->rowBytes), srcImage->pixels + ((size_t)j * srcImage->rowBytes), bytesPerRow); |
707 | 0 | } |
708 | 0 | } else if (rotation->angle == 1) { |
709 | | // 90 degrees anti-clockwise. |
710 | 0 | for (uint32_t j = 0; j < srcImage->height; ++j) { |
711 | 0 | for (uint32_t i = 0; i < srcImage->width; ++i) { |
712 | | // Source pixel at (i, j) goes to destination pixel at (j, srcImage->width - 1 - i). |
713 | 0 | memcpy(dstImage->pixels + ((size_t)(srcImage->width - 1 - i) * dstImage->rowBytes) + ((size_t)j * bytesPerPixel), |
714 | 0 | srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel), |
715 | 0 | bytesPerPixel); |
716 | 0 | } |
717 | 0 | } |
718 | 0 | } else if (rotation->angle == 2) { |
719 | | // 180 degrees. |
720 | 0 | for (uint32_t j = 0; j < srcImage->height; ++j) { |
721 | 0 | for (uint32_t i = 0; i < srcImage->width; ++i) { |
722 | | // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, srcImage->height - 1 - j). |
723 | 0 | memcpy(dstImage->pixels + ((size_t)(srcImage->height - 1 - j) * dstImage->rowBytes) + |
724 | 0 | ((size_t)(srcImage->width - 1 - i) * bytesPerPixel), |
725 | 0 | srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel), |
726 | 0 | bytesPerPixel); |
727 | 0 | } |
728 | 0 | } |
729 | 0 | } else if (rotation->angle == 3) { |
730 | | // 90 degrees clockwise. |
731 | 0 | for (uint32_t j = 0; j < srcImage->height; ++j) { |
732 | 0 | for (uint32_t i = 0; i < srcImage->width; ++i) { |
733 | | // Source pixel at (i, j) goes to destination pixel at (srcImage->width - 1 - i, j). |
734 | 0 | memcpy(dstImage->pixels + ((size_t)i * dstImage->rowBytes) + ((size_t)(srcImage->height - 1 - j) * bytesPerPixel), |
735 | 0 | srcImage->pixels + ((size_t)j * srcImage->rowBytes) + ((size_t)i * bytesPerPixel), |
736 | 0 | bytesPerPixel); |
737 | 0 | } |
738 | 0 | } |
739 | 0 | } else { |
740 | 0 | return AVIF_RESULT_INVALID_ARGUMENT; // Invalid angle. |
741 | 0 | } |
742 | 0 | return AVIF_RESULT_OK; |
743 | 0 | } |
744 | | |
745 | | avifResult avifRGBImageMirror(avifRGBImage * image, const avifImageMirror * mirror) |
746 | 0 | { |
747 | 0 | if (mirror->axis == 0) { // Horizontal axis. |
748 | 0 | const uint32_t bytesPerPixel = avifRGBImagePixelSize(image); |
749 | | // May be less than image->rowBytes e.g. if image is a cropped view. |
750 | 0 | const size_t bytesPerRowToMove = (size_t)bytesPerPixel * image->width; |
751 | | // Top-to-bottom |
752 | 0 | uint8_t * tempRow = (uint8_t *)avifAlloc(bytesPerRowToMove); |
753 | 0 | if (!tempRow) { |
754 | 0 | return AVIF_RESULT_OUT_OF_MEMORY; |
755 | 0 | } |
756 | 0 | for (uint32_t y = 0; y < image->height / 2; ++y) { |
757 | 0 | uint8_t * row1 = &image->pixels[(size_t)y * image->rowBytes]; |
758 | 0 | uint8_t * row2 = &image->pixels[(size_t)(image->height - 1 - y) * image->rowBytes]; |
759 | 0 | memcpy(tempRow, row1, bytesPerRowToMove); |
760 | 0 | memcpy(row1, row2, bytesPerRowToMove); |
761 | 0 | memcpy(row2, tempRow, bytesPerRowToMove); |
762 | 0 | } |
763 | 0 | avifFree(tempRow); |
764 | 0 | } else if (mirror->axis == 1) { // Vertical axis. |
765 | 0 | const uint32_t bytesPerPixel = avifRGBImagePixelSize(image); |
766 | 0 | uint8_t tempPixel[8]; // Max pixel size should be 8 bytes (RGBA 16-bit). |
767 | 0 | if (bytesPerPixel > sizeof(tempPixel)) { |
768 | 0 | return AVIF_RESULT_INVALID_ARGUMENT; |
769 | 0 | } |
770 | 0 | for (uint32_t y = 0; y < image->height; ++y) { |
771 | 0 | uint8_t * row = &image->pixels[(size_t)y * image->rowBytes]; |
772 | 0 | for (uint32_t x = 0; x < image->width / 2; ++x) { |
773 | 0 | uint8_t * pixel1 = &row[(size_t)x * bytesPerPixel]; |
774 | 0 | uint8_t * pixel2 = &row[(size_t)(image->width - 1 - x) * bytesPerPixel]; |
775 | 0 | memcpy(tempPixel, pixel1, bytesPerPixel); |
776 | 0 | memcpy(pixel1, pixel2, bytesPerPixel); |
777 | 0 | memcpy(pixel2, tempPixel, bytesPerPixel); |
778 | 0 | } |
779 | 0 | } |
780 | 0 | } else { |
781 | 0 | return AVIF_RESULT_INVALID_ARGUMENT; // Invalid axis value. |
782 | 0 | } |
783 | | |
784 | 0 | return AVIF_RESULT_OK; |
785 | 0 | } |
786 | | |
787 | | avifResult avifApplyTransforms(avifRGBImage * dstView, avifRGBImage * srcImage, const avifImage * avif) |
788 | 0 | { |
789 | | // ISO/IEC 23000-22 (MIAF), Section 7.3.6.7: |
790 | | // These properties, if used, shall be indicated to be applied in the following order: |
791 | | // clean aperture first, then rotation, then mirror. |
792 | 0 | *dstView = *srcImage; |
793 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { |
794 | 0 | avifCropRect cropRect; |
795 | 0 | avifDiagnostics diag; |
796 | 0 | if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) && |
797 | 0 | (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) { |
798 | 0 | avifRGBImageSetViewRect(dstView, srcImage, &cropRect); |
799 | 0 | } else { |
800 | 0 | fprintf(stderr, "Invalid clean aperture box\n"); |
801 | 0 | return AVIF_RESULT_INVALID_ARGUMENT; |
802 | 0 | } |
803 | 0 | } |
804 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_IROT && avif->irot.angle != 0) { |
805 | 0 | avifRGBImage tmpRgbImage; |
806 | 0 | avifResult result = avifRGBImageRotate(&tmpRgbImage, dstView, &avif->irot); |
807 | 0 | if (result != AVIF_RESULT_OK) { |
808 | 0 | fprintf(stderr, "Failed to apply rotation\n"); |
809 | 0 | avifRGBImageFreePixels(&tmpRgbImage); |
810 | 0 | return result; |
811 | 0 | } |
812 | | // We assume that srcImage owned its pixels and free them before replacing it with tmpRgbImage. |
813 | 0 | avifRGBImageFreePixels(srcImage); |
814 | 0 | *srcImage = tmpRgbImage; |
815 | 0 | *dstView = *srcImage; |
816 | 0 | } |
817 | 0 | if (avif->transformFlags & AVIF_TRANSFORM_IMIR) { |
818 | 0 | avifResult result = avifRGBImageMirror(dstView, &avif->imir); |
819 | 0 | if (result != AVIF_RESULT_OK) { |
820 | 0 | fprintf(stderr, "Failed to apply mirror\n"); |
821 | 0 | return result; |
822 | 0 | } |
823 | 0 | } |
824 | 0 | return AVIF_RESULT_OK; |
825 | 0 | } |
826 | | |
827 | | avifResult avifImageCreateView(avifImage * dstImage, const avifImage * srcImage) |
828 | 0 | { |
829 | 0 | avifResult res = AVIF_RESULT_OK; |
830 | 0 | if (!dstImage || !srcImage) { |
831 | 0 | return AVIF_RESULT_INVALID_ARGUMENT; |
832 | 0 | } |
833 | | |
834 | 0 | avifCropRect rect = { 0, 0, srcImage->width, srcImage->height }; |
835 | 0 | res = avifImageSetViewRect(dstImage, srcImage, &rect); |
836 | 0 | if (res != AVIF_RESULT_OK) { |
837 | 0 | return res; |
838 | 0 | } |
839 | | |
840 | 0 | if (srcImage->icc.size > 0) { |
841 | 0 | res = avifImageSetProfileICC(dstImage, srcImage->icc.data, srcImage->icc.size); |
842 | 0 | if (res != AVIF_RESULT_OK) { |
843 | 0 | return res; |
844 | 0 | } |
845 | 0 | } |
846 | | |
847 | | // Using avifRWDataSet directly to match the internal avifImageCopy() behavior. |
848 | | // This avoids re-extracting Exif orientation into irot/imir on an already decoded image. |
849 | 0 | res = avifRWDataSet(&dstImage->exif, srcImage->exif.data, srcImage->exif.size); |
850 | 0 | if (res != AVIF_RESULT_OK) { |
851 | 0 | return res; |
852 | 0 | } |
853 | 0 | res = avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size); |
854 | 0 | if (res != AVIF_RESULT_OK) { |
855 | 0 | return res; |
856 | 0 | } |
857 | | |
858 | 0 | for (size_t i = 0; i < srcImage->numProperties; ++i) { |
859 | 0 | if (memcmp(srcImage->properties[i].boxtype, "uuid", 4) == 0) { |
860 | 0 | res = avifImageAddUUIDProperty(dstImage, |
861 | 0 | srcImage->properties[i].usertype, |
862 | 0 | srcImage->properties[i].boxPayload.data, |
863 | 0 | srcImage->properties[i].boxPayload.size); |
864 | 0 | } else { |
865 | 0 | res = avifImageAddOpaqueProperty(dstImage, |
866 | 0 | srcImage->properties[i].boxtype, |
867 | 0 | srcImage->properties[i].boxPayload.data, |
868 | 0 | srcImage->properties[i].boxPayload.size); |
869 | 0 | } |
870 | 0 | if (res != AVIF_RESULT_OK) { |
871 | 0 | return res; |
872 | 0 | } |
873 | 0 | } |
874 | | |
875 | 0 | if (srcImage->gainMap) { |
876 | 0 | dstImage->gainMap = avifGainMapCreate(); |
877 | 0 | if (!dstImage->gainMap) { |
878 | 0 | return AVIF_RESULT_OUT_OF_MEMORY; |
879 | 0 | } |
880 | | |
881 | | // Copy all gain map scalars at once |
882 | 0 | *dstImage->gainMap = *srcImage->gainMap; |
883 | | // Reset pointers to prevent shared ownership and double-free |
884 | 0 | dstImage->gainMap->image = NULL; |
885 | 0 | dstImage->gainMap->altICC.data = NULL; |
886 | 0 | dstImage->gainMap->altICC.size = 0; |
887 | |
|
888 | 0 | if (srcImage->gainMap->altICC.size > 0) { |
889 | 0 | res = avifRWDataSet(&dstImage->gainMap->altICC, srcImage->gainMap->altICC.data, srcImage->gainMap->altICC.size); |
890 | 0 | if (res != AVIF_RESULT_OK) { |
891 | 0 | return res; |
892 | 0 | } |
893 | 0 | } |
894 | | |
895 | 0 | if (srcImage->gainMap->image) { |
896 | 0 | dstImage->gainMap->image = avifImageCreateEmpty(); |
897 | 0 | if (!dstImage->gainMap->image) { |
898 | 0 | return AVIF_RESULT_OUT_OF_MEMORY; |
899 | 0 | } |
900 | 0 | res = avifImageCreateView(dstImage->gainMap->image, srcImage->gainMap->image); |
901 | 0 | if (res != AVIF_RESULT_OK) { |
902 | 0 | return res; |
903 | 0 | } |
904 | 0 | } |
905 | 0 | } |
906 | | |
907 | 0 | return AVIF_RESULT_OK; |
908 | 0 | } |