Skip to content

Commit 2f0157d

Browse files
committed
feat(heif): Monochrome channel read and write support, fix crashes
Implement support for reading and writing monochrome. Reading requires libheif 1.17+ for heif_image_handle_get_preferred_decoding_colorspace. Previously Writing a single channel image would cause an exception due to wrong parameters, but close() would continue writing the image. Destroy m_ctx on exception to prevent that. Signed-off-by: Brecht Van Lommel <brecht@blender.org>
1 parent 8a39d36 commit 2f0157d

12 files changed

+371
-23
lines changed

src/heif.imageio/heifinput.cpp

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,40 @@ HeifInput::seek_subimage(int subimage, int miplevel)
262262
}
263263

264264
m_has_alpha = m_ihandle.has_alpha_channel();
265-
auto chroma = m_has_alpha ? (m_bitdepth > 8)
266-
? littleendian()
267-
? heif_chroma_interleaved_RRGGBBAA_LE
268-
: heif_chroma_interleaved_RRGGBBAA_BE
269-
: heif_chroma_interleaved_RGBA
270-
: (m_bitdepth > 8) ? littleendian()
271-
? heif_chroma_interleaved_RRGGBB_LE
272-
: heif_chroma_interleaved_RRGGBB_BE
273-
: heif_chroma_interleaved_RGB;
265+
266+
bool is_monochrome = false;
267+
268+
#if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 17, 0, 0)
269+
heif_colorspace preferred_colorspace = heif_colorspace_undefined;
270+
heif_chroma preferred_chroma = heif_chroma_undefined;
271+
272+
if (heif_image_handle_get_preferred_decoding_colorspace(
273+
m_ihandle.get_raw_image_handle(), &preferred_colorspace,
274+
&preferred_chroma)
275+
.code
276+
== heif_error_Ok) {
277+
is_monochrome = preferred_colorspace == heif_colorspace_monochrome;
278+
}
279+
#endif
280+
281+
const heif_chroma chroma
282+
= (is_monochrome) ? heif_chroma_monochrome
283+
: m_has_alpha ? (m_bitdepth > 8)
284+
? littleendian()
285+
? heif_chroma_interleaved_RRGGBBAA_LE
286+
: heif_chroma_interleaved_RRGGBBAA_BE
287+
: heif_chroma_interleaved_RGBA
288+
: (m_bitdepth > 8) ? littleendian()
289+
? heif_chroma_interleaved_RRGGBB_LE
290+
: heif_chroma_interleaved_RRGGBB_BE
291+
: heif_chroma_interleaved_RGB;
292+
const heif_colorspace colorspace = is_monochrome
293+
? heif_colorspace_monochrome
294+
: heif_colorspace_RGB;
295+
const heif_channel channel = is_monochrome ? heif_channel_Y
296+
: heif_channel_interleaved;
297+
const int nchannels = is_monochrome ? 1 : m_has_alpha ? 4 : 3;
298+
274299
#if 0
275300
try {
276301
m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma);
@@ -290,8 +315,8 @@ HeifInput::seek_subimage(int subimage, int miplevel)
290315
// print("Got decoding options version {}\n", options->version);
291316
struct heif_image* img_tmp = nullptr;
292317
struct heif_error herr = heif_decode_image(m_ihandle.get_raw_image_handle(),
293-
&img_tmp, heif_colorspace_RGB,
294-
chroma, options.get());
318+
&img_tmp, colorspace, chroma,
319+
options.get());
295320
if (img_tmp)
296321
m_himage = heif::Image(img_tmp);
297322
if (herr.code != heif_error_Ok || !img_tmp) {
@@ -301,9 +326,8 @@ HeifInput::seek_subimage(int subimage, int miplevel)
301326
}
302327
#endif
303328

304-
m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved),
305-
m_himage.get_height(heif_channel_interleaved),
306-
m_has_alpha ? 4 : 3,
329+
m_spec = ImageSpec(m_himage.get_width(channel),
330+
m_himage.get_height(channel), nchannels,
307331
(m_bitdepth > 8) ? TypeUInt16 : TypeUInt8);
308332

309333
if (m_bitdepth > 8) {
@@ -492,12 +516,13 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
492516
#else
493517
int ystride = 0;
494518
#endif
519+
const heif_channel channel = m_spec.nchannels == 1
520+
? heif_channel_Y
521+
: heif_channel_interleaved;
495522
#if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0)
496-
const uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved,
497-
&ystride);
523+
const uint8_t* hdata = m_himage.get_plane2(channel, &ystride);
498524
#else
499-
const uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved,
500-
&ystride);
525+
const uint8_t* hdata = m_himage.get_plane(channel, &ystride);
501526
#endif
502527
if (!hdata) {
503528
errorfmt("Unknown read error");

src/heif.imageio/heifoutput.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,16 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec,
137137
(m_bitdepth == 8) ? heif_chroma_interleaved_RGBA
138138
: littleendian() ? heif_chroma_interleaved_RRGGBBAA_LE
139139
: heif_chroma_interleaved_RRGGBBAA_BE };
140-
m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB,
140+
const heif_colorspace colorspace = (m_spec.nchannels == 1)
141+
? heif_colorspace_monochrome
142+
: heif_colorspace_RGB;
143+
const heif_channel channel = (m_spec.nchannels == 1)
144+
? heif_channel_Y
145+
: heif_channel_interleaved;
146+
147+
m_himage.create(newspec.width, newspec.height, colorspace,
141148
chromas[m_spec.nchannels]);
142-
m_himage.add_plane(heif_channel_interleaved, newspec.width,
143-
newspec.height, m_bitdepth);
149+
m_himage.add_plane(channel, newspec.width, newspec.height, m_bitdepth);
144150

145151
auto compqual = m_spec.decode_compression_metadata("", 75);
146152
auto extension = Filesystem::extension(m_filename);
@@ -153,10 +159,12 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec,
153159
} catch (const heif::Error& err) {
154160
std::string e = err.get_message();
155161
errorfmt("{}", e.empty() ? "unknown exception" : e.c_str());
162+
m_ctx.reset();
156163
return false;
157164
} catch (const std::exception& err) {
158165
std::string e = err.what();
159166
errorfmt("{}", e.empty() ? "unknown exception" : e.c_str());
167+
m_ctx.reset();
160168
return false;
161169
}
162170

@@ -180,10 +188,13 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data,
180188
#else
181189
int hystride = 0;
182190
#endif
191+
const heif_channel hchannel = (m_spec.nchannels == 1)
192+
? heif_channel_Y
193+
: heif_channel_interleaved;
183194
#if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0)
184-
uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved, &hystride);
195+
uint8_t* hdata = m_himage.get_plane2(hchannel, &hystride);
185196
#else
186-
uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride);
197+
uint8_t* hdata = m_himage.get_plane(hchannel, &hystride);
187198
#endif
188199
hdata += hystride * (y - m_spec.y);
189200
if (m_bitdepth == 10 || m_bitdepth == 12) {

testsuite/heif/ref/out-libheif1.12-orient.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic
182182
GPS:Longitude: 1, 49, 34.0187
183183
GPS:LongitudeRef: "E"
184184
oiio:ColorSpace: "srgb_rec709_scene"
185+
Reading mono-8bit.avif
186+
mono-8bit.avif : 64 x 64, 3 channel, uint10 heif
187+
SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE
188+
channel list: R, G, B
189+
CICP: 2, 2, 6, 1
190+
oiio:BitsPerSample: 10
191+
oiio:ColorSpace: "srgb_rec709_scene"
192+
Reading mono-10bit.avif
193+
mono-10bit.avif : 64 x 64, 3 channel, uint10 heif
194+
SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE
195+
channel list: R, G, B
196+
CICP: 2, 2, 6, 1
197+
oiio:BitsPerSample: 10
198+
oiio:ColorSpace: "srgb_rec709_scene"

testsuite/heif/ref/out-libheif1.21-with-av1.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,15 @@ Reading ../oiio-images/heif/sewing-threads.heic
162162
GPS:Longitude: 1, 49, 34.0187
163163
GPS:LongitudeRef: "E"
164164
oiio:ColorSpace: "srgb_rec709_scene"
165+
Reading mono-8bit.avif
166+
mono-8bit.avif : 64 x 64, 1 channel, uint10 heif
167+
SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B
168+
channel list: Y
169+
oiio:BitsPerSample: 10
170+
oiio:ColorSpace: "srgb_rec709_scene"
171+
Reading mono-10bit.avif
172+
mono-10bit.avif : 64 x 64, 1 channel, uint10 heif
173+
SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B
174+
channel list: Y
175+
oiio:BitsPerSample: 10
176+
oiio:ColorSpace: "srgb_rec709_scene"
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
Reading ref/IMG_7702_small.heic
2+
ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif
3+
SHA-1: 2380C124F8338910013FEA75C9C64C23567A3156
4+
channel list: R, G, B
5+
DateTime: "2019:01:21 16:10:54"
6+
ExposureTime: 0.030303
7+
FNumber: 1.8
8+
Make: "Apple"
9+
Model: "iPhone 7"
10+
Orientation: 1 (normal)
11+
ResolutionUnit: 2 (inches)
12+
Software: "12.1.2"
13+
XResolution: 72
14+
YResolution: 72
15+
Exif:ApertureValue: 1.69599 (f/1.8)
16+
Exif:BrightnessValue: 3.99501
17+
Exif:ColorSpace: 65535
18+
Exif:DateTimeDigitized: "2019:01:21 16:10:54"
19+
Exif:DateTimeOriginal: "2019:01:21 16:10:54"
20+
Exif:ExifVersion: "0221"
21+
Exif:ExposureBiasValue: 0
22+
Exif:ExposureMode: 0 (auto)
23+
Exif:ExposureProgram: 2 (normal program)
24+
Exif:Flash: 24 (no flash, auto flash)
25+
Exif:FlashPixVersion: "0100"
26+
Exif:FocalLength: 3.99 (3.99 mm)
27+
Exif:FocalLengthIn35mmFilm: 28
28+
Exif:LensMake: "Apple"
29+
Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8"
30+
Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8
31+
Exif:MeteringMode: 5 (pattern)
32+
Exif:PhotographicSensitivity: 20
33+
Exif:PixelXDimension: 4032
34+
Exif:PixelYDimension: 3024
35+
Exif:SceneCaptureType: 0 (standard)
36+
Exif:SensingMethod: 2 (1-chip color area)
37+
Exif:ShutterSpeedValue: 5.03599 (1/32 s)
38+
Exif:SubsecTimeDigitized: "006"
39+
Exif:SubsecTimeOriginal: "006"
40+
Exif:WhiteBalance: 0 (auto)
41+
oiio:ColorSpace: "srgb_rec709_scene"
42+
Reading ref/Chimera-AV1-8bit-162.avif
43+
ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
44+
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
45+
channel list: R, G, B
46+
oiio:ColorSpace: "srgb_rec709_scene"
47+
Reading ref/test-10bit.avif
48+
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
49+
SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA
50+
channel list: R, G, B, A
51+
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
52+
Exif:ExifVersion: "0230"
53+
Exif:FlashPixVersion: "0100"
54+
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
55+
heif:UnassociatedAlpha: 1
56+
oiio:BitsPerSample: 10
57+
oiio:ColorSpace: "srgb_rec709_scene"
58+
Reading cicp_pq.avif
59+
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
60+
SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540
61+
channel list: R, G, B, A
62+
CICP: 9, 16, 9, 1
63+
Exif:ExifVersion: "0230"
64+
Exif:FlashPixVersion: "0100"
65+
heif:UnassociatedAlpha: 1
66+
oiio:BitsPerSample: 10
67+
oiio:ColorSpace: "pq_rec2020_display"
68+
Reading colorspace_hlg.avif
69+
colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif
70+
SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540
71+
channel list: R, G, B, A
72+
CICP: 9, 18, 9, 1
73+
Exif:ExifVersion: "0230"
74+
Exif:FlashPixVersion: "0100"
75+
heif:UnassociatedAlpha: 1
76+
oiio:BitsPerSample: 10
77+
oiio:ColorSpace: "hlg_rec2020_display"
78+
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
79+
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
80+
SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C
81+
channel list: R, G, B
82+
DateTime: "2023:09:28 09:44:03"
83+
ExposureTime: 0.0135135
84+
FNumber: 2.4
85+
Make: "Apple"
86+
Model: "iPhone 12 Pro"
87+
Orientation: 1 (normal)
88+
ResolutionUnit: 2 (inches)
89+
Software: "16.7"
90+
XResolution: 72
91+
YResolution: 72
92+
Exif:ApertureValue: 2.52607 (f/2.4)
93+
Exif:BrightnessValue: 2.7506
94+
Exif:ColorSpace: 65535
95+
Exif:CompositeImage: 2
96+
Exif:DateTimeDigitized: "2023:09:28 09:44:03"
97+
Exif:DateTimeOriginal: "2023:09:28 09:44:03"
98+
Exif:DigitalZoomRatio: 1.3057
99+
Exif:ExifVersion: "0232"
100+
Exif:ExposureBiasValue: 0
101+
Exif:ExposureMode: 0 (auto)
102+
Exif:ExposureProgram: 2 (normal program)
103+
Exif:Flash: 16 (no flash, flash suppression)
104+
Exif:FocalLength: 1.54 (1.54 mm)
105+
Exif:FocalLengthIn35mmFilm: 17
106+
Exif:LensMake: "Apple"
107+
Exif:LensModel: "iPhone 12 Pro back triple camera 1.54mm f/2.4"
108+
Exif:LensSpecification: 1.54, 6, 1.6, 2.4
109+
Exif:MeteringMode: 5 (pattern)
110+
Exif:OffsetTime: "+02:00"
111+
Exif:OffsetTimeDigitized: "+02:00"
112+
Exif:OffsetTimeOriginal: "+02:00"
113+
Exif:PhotographicSensitivity: 320
114+
Exif:PixelXDimension: 4032
115+
Exif:PixelYDimension: 3024
116+
Exif:SensingMethod: 2 (1-chip color area)
117+
Exif:ShutterSpeedValue: 6.20983 (1/74 s)
118+
Exif:SubsecTimeDigitized: "886"
119+
Exif:SubsecTimeOriginal: "886"
120+
Exif:WhiteBalance: 0 (auto)
121+
GPS:Altitude: 3.24105 (3.24105 m)
122+
GPS:AltitudeRef: 0 (above sea level)
123+
GPS:DateStamp: "2023:09:28"
124+
GPS:DestBearing: 90.2729
125+
GPS:DestBearingRef: "T" (true north)
126+
GPS:HPositioningError: 5.1893
127+
GPS:ImgDirection: 90.2729
128+
GPS:ImgDirectionRef: "T" (true north)
129+
GPS:Latitude: 41, 50, 58.43
130+
GPS:LatitudeRef: "N"
131+
GPS:Longitude: 3, 7, 31.98
132+
GPS:LongitudeRef: "E"
133+
GPS:Speed: 0.171966
134+
GPS:SpeedRef: "K" (km/hour)
135+
oiio:ColorSpace: "srgb_rec709_scene"
136+
oiio:OriginalOrientation: 6
137+
Reading ../oiio-images/heif/sewing-threads.heic
138+
../oiio-images/heif/sewing-threads.heic : 4000 x 3000, 3 channel, uint8 heif
139+
SHA-1: 44551A0A8AADD2C71B504681F2BAE3F7863EF9B9
140+
channel list: R, G, B
141+
DateTime: "2023:12:12 18:39:16"
142+
ExposureTime: 0.04
143+
FNumber: 1.8
144+
Make: "samsung"
145+
Model: "SM-A326B"
146+
Orientation: 1 (normal)
147+
ResolutionUnit: 2 (inches)
148+
Software: "A326BXXS8CWK2"
149+
XResolution: 72
150+
YResolution: 72
151+
Exif:ApertureValue: 1.69 (f/1.8)
152+
Exif:BrightnessValue: 1.19
153+
Exif:ColorSpace: 1
154+
Exif:DateTimeDigitized: "2023:12:12 18:39:16"
155+
Exif:DateTimeOriginal: "2023:12:12 18:39:16"
156+
Exif:DigitalZoomRatio: 1
157+
Exif:ExifVersion: "0220"
158+
Exif:ExposureBiasValue: 0
159+
Exif:ExposureMode: 0 (auto)
160+
Exif:ExposureProgram: 2 (normal program)
161+
Exif:Flash: 0 (no flash)
162+
Exif:FocalLength: 4.6 (4.6 mm)
163+
Exif:FocalLengthIn35mmFilm: 25
164+
Exif:MaxApertureValue: 1.69 (f/1.8)
165+
Exif:MeteringMode: 2 (center-weighted average)
166+
Exif:OffsetTime: "+01:00"
167+
Exif:OffsetTimeOriginal: "+01:00"
168+
Exif:PhotographicSensitivity: 500
169+
Exif:PixelXDimension: 4000
170+
Exif:PixelYDimension: 3000
171+
Exif:SceneCaptureType: 0 (standard)
172+
Exif:ShutterSpeedValue: 0.04 (1/1 s)
173+
Exif:SubsecTime: "576"
174+
Exif:SubsecTimeDigitized: "576"
175+
Exif:SubsecTimeOriginal: "576"
176+
Exif:WhiteBalance: 0 (auto)
177+
Exif:YCbCrPositioning: 1
178+
GPS:Altitude: 292 (292 m)
179+
GPS:AltitudeRef: 0 (above sea level)
180+
GPS:Latitude: 41, 43, 33.821
181+
GPS:LatitudeRef: "N"
182+
GPS:Longitude: 1, 49, 34.0187
183+
GPS:LongitudeRef: "E"
184+
oiio:ColorSpace: "srgb_rec709_scene"
185+
Reading mono-8bit.avif
186+
mono-8bit.avif : 64 x 64, 1 channel, uint10 heif
187+
SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B
188+
channel list: Y
189+
oiio:BitsPerSample: 10
190+
oiio:ColorSpace: "srgb_rec709_scene"
191+
Reading mono-10bit.avif
192+
mono-10bit.avif : 64 x 64, 1 channel, uint10 heif
193+
SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B
194+
channel list: Y
195+
oiio:BitsPerSample: 10
196+
oiio:ColorSpace: "srgb_rec709_scene"

testsuite/heif/ref/out-libheif1.4.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic
182182
GPS:Longitude: 1, 49, 34.0187
183183
GPS:LongitudeRef: "E"
184184
oiio:ColorSpace: "srgb_rec709_scene"
185+
Reading mono-8bit.avif
186+
mono-8bit.avif : 64 x 64, 3 channel, uint10 heif
187+
SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE
188+
channel list: R, G, B
189+
CICP: 2, 2, 6, 1
190+
oiio:BitsPerSample: 10
191+
oiio:ColorSpace: "srgb_rec709_scene"
192+
Reading mono-10bit.avif
193+
mono-10bit.avif : 64 x 64, 3 channel, uint10 heif
194+
SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE
195+
channel list: R, G, B
196+
CICP: 2, 2, 6, 1
197+
oiio:BitsPerSample: 10
198+
oiio:ColorSpace: "srgb_rec709_scene"

0 commit comments

Comments
 (0)