Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: HEIF Driver
4 : * Author: Brad Hards <bradh@frogmouth.net>
5 : *
6 : ******************************************************************************
7 : * Copyright (c) 2024, Brad Hards <bradh@frogmouth.net>
8 : *
9 : * SPDX-License-Identifier: MIT
10 : ****************************************************************************/
11 :
12 : #include "heifdataset.h"
13 : #include <algorithm>
14 : #include <cstddef>
15 : #include <cstdint>
16 : #include <cstring>
17 : #include <iostream>
18 : #include <memory>
19 : #include <vector>
20 :
21 : #include <libheif/heif.h>
22 :
23 : #include "cpl_error.h"
24 :
25 : #ifdef HAS_CUSTOM_FILE_WRITER
26 :
27 : // Same default as libheif encoder example
28 : constexpr int DEFAULT_QUALITY = 50;
29 :
30 2 : static CPLErr mapColourInterpretation(GDALColorInterp colourInterpretation,
31 : heif_channel *channel)
32 : {
33 2 : switch (colourInterpretation)
34 : {
35 0 : case GCI_RedBand:
36 0 : *channel = heif_channel_R;
37 0 : return CE_None;
38 0 : case GCI_GreenBand:
39 0 : *channel = heif_channel_G;
40 0 : return CE_None;
41 0 : case GCI_BlueBand:
42 0 : *channel = heif_channel_B;
43 0 : return CE_None;
44 0 : case GCI_AlphaBand:
45 0 : *channel = heif_channel_Alpha;
46 0 : return CE_None;
47 2 : default:
48 2 : return CE_Failure;
49 : }
50 : }
51 :
52 2 : static heif_compression_format getCompressionType(CSLConstList papszOptions)
53 : {
54 2 : const char *pszValue = CSLFetchNameValue(papszOptions, "CODEC");
55 2 : if (pszValue == nullptr)
56 : {
57 2 : return heif_compression_HEVC;
58 : }
59 0 : if (strcmp(pszValue, "HEVC") == 0)
60 : {
61 0 : return heif_compression_HEVC;
62 : }
63 : #if LIBHEIF_HAVE_VERSION(1, 7, 0)
64 : if (strcmp(pszValue, "AV1") == 0)
65 : {
66 : return heif_compression_AV1;
67 : }
68 : #endif
69 : #if LIBHEIF_HAVE_VERSION(1, 17, 0)
70 : if (strcmp(pszValue, "JPEG") == 0)
71 : {
72 : return heif_compression_JPEG;
73 : }
74 : #endif
75 : #if LIBHEIF_HAVE_VERSION(1, 17, 0)
76 : if (strcmp(pszValue, "JPEG2000") == 0)
77 : {
78 : return heif_compression_JPEG2000;
79 : }
80 : #endif
81 : #if LIBHEIF_HAVE_VERSION(1, 16, 0)
82 : if (strcmp(pszValue, "UNCOMPRESSED") == 0)
83 : {
84 : return heif_compression_uncompressed;
85 : }
86 : #endif
87 : #if LIBHEIF_HAVE_VERSION(1, 18, 0)
88 : if (strcmp(pszValue, "VVC") == 0)
89 : {
90 : return heif_compression_VVC;
91 : }
92 : #endif
93 0 : CPLError(CE_Warning, CPLE_IllegalArg,
94 : "CODEC=%s value not recognised, ignoring.", pszValue);
95 0 : return heif_compression_HEVC;
96 : }
97 :
98 2 : static void setEncoderParameters(heif_encoder *encoder,
99 : CSLConstList papszOptions)
100 : {
101 2 : const char *pszValue = CSLFetchNameValue(papszOptions, "QUALITY");
102 2 : int nQuality = DEFAULT_QUALITY;
103 2 : if (pszValue != nullptr)
104 : {
105 0 : nQuality = atoi(pszValue);
106 0 : if ((nQuality < 0) || (nQuality > 100))
107 : {
108 0 : CPLError(CE_Warning, CPLE_IllegalArg,
109 : "QUALITY=%s value not recognised, ignoring.", pszValue);
110 0 : nQuality = DEFAULT_QUALITY;
111 : }
112 : }
113 2 : heif_encoder_set_lossy_quality(encoder, nQuality);
114 2 : }
115 :
116 0 : heif_error GDALHEIFDataset::VFS_WriterCallback(struct heif_context *,
117 : const void *data, size_t size,
118 : void *userdata)
119 : {
120 0 : VSILFILE *fp = static_cast<VSILFILE *>(userdata);
121 0 : size_t bytesWritten = VSIFWriteL(data, 1, size, fp);
122 : heif_error result;
123 0 : if (bytesWritten == size)
124 : {
125 0 : result.code = heif_error_Ok;
126 0 : result.subcode = heif_suberror_Unspecified;
127 0 : result.message = "Success";
128 : }
129 : else
130 : {
131 0 : result.code = heif_error_Encoding_error;
132 0 : result.subcode = heif_suberror_Cannot_write_output_data;
133 0 : result.message = "Not all data written";
134 : }
135 0 : return result;
136 : }
137 :
138 : /************************************************************************/
139 : /* CreateCopy() */
140 : /************************************************************************/
141 : GDALDataset *
142 18 : GDALHEIFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS, int,
143 : CPL_UNUSED char **papszOptions,
144 : CPL_UNUSED GDALProgressFunc pfnProgress,
145 : CPL_UNUSED void *pProgressData)
146 : {
147 18 : if (pfnProgress == nullptr)
148 0 : pfnProgress = GDALDummyProgress;
149 :
150 18 : int nBands = poSrcDS->GetRasterCount();
151 18 : if ((nBands != 3) && (nBands != 4))
152 : {
153 16 : CPLError(CE_Failure, CPLE_NotSupported,
154 : "Driver only supports source dataset with 3 or 4 bands.");
155 16 : return nullptr;
156 : }
157 : // TODO: more sanity checks
158 :
159 2 : heif_context *ctx = heif_context_alloc();
160 : heif_encoder *encoder;
161 2 : heif_compression_format codec = getCompressionType(papszOptions);
162 : struct heif_error err;
163 2 : err = heif_context_get_encoder_for_format(ctx, codec, &encoder);
164 2 : if (err.code)
165 : {
166 0 : heif_context_free(ctx);
167 : // TODO: get the error message and printf
168 0 : CPLError(CE_Failure, CPLE_AppDefined,
169 : "Failed to create libheif encoder.");
170 0 : return nullptr;
171 : }
172 :
173 2 : setEncoderParameters(encoder, papszOptions);
174 :
175 : heif_image *image;
176 :
177 : err =
178 : heif_image_create(poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(),
179 2 : heif_colorspace_RGB, heif_chroma_444, &image);
180 2 : if (err.code)
181 : {
182 0 : heif_encoder_release(encoder);
183 0 : heif_context_free(ctx);
184 0 : CPLError(CE_Failure, CPLE_AppDefined,
185 : "Failed to create libheif input image.\n");
186 0 : return nullptr;
187 : }
188 :
189 2 : for (auto &&poBand : poSrcDS->GetBands())
190 : {
191 2 : if (poBand->GetRasterDataType() != GDT_Byte)
192 : {
193 0 : heif_image_release(image);
194 0 : heif_encoder_release(encoder);
195 0 : heif_context_free(ctx);
196 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unsupported data type.");
197 2 : return nullptr;
198 : }
199 : heif_channel channel;
200 : auto mapError =
201 2 : mapColourInterpretation(poBand->GetColorInterpretation(), &channel);
202 2 : if (mapError != CE_None)
203 : {
204 2 : heif_image_release(image);
205 2 : heif_encoder_release(encoder);
206 2 : heif_context_free(ctx);
207 2 : CPLError(CE_Failure, CPLE_NotSupported,
208 : "Driver does not support bands other than RGBA yet.");
209 2 : return nullptr;
210 : }
211 : err = heif_image_add_plane(image, channel, poSrcDS->GetRasterXSize(),
212 0 : poSrcDS->GetRasterYSize(), 8);
213 0 : if (err.code)
214 : {
215 0 : heif_image_release(image);
216 0 : heif_encoder_release(encoder);
217 0 : heif_context_free(ctx);
218 0 : CPLError(CE_Failure, CPLE_AppDefined,
219 : "Failed to add image plane to libheif input image.");
220 0 : return nullptr;
221 : }
222 : int stride;
223 0 : uint8_t *p = heif_image_get_plane(image, channel, &stride);
224 0 : auto eErr = poBand->RasterIO(
225 : GF_Read, 0, 0, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(),
226 : p, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), GDT_Byte,
227 : 0, stride, nullptr);
228 :
229 0 : if (eErr != CE_None)
230 : {
231 0 : heif_image_release(image);
232 0 : heif_encoder_release(encoder);
233 0 : heif_context_free(ctx);
234 0 : return nullptr;
235 : }
236 : }
237 :
238 : // TODO: set options based on creation options
239 0 : heif_encoding_options *encoding_options = nullptr;
240 :
241 : heif_image_handle *out_image_handle;
242 :
243 : heif_context_encode_image(ctx, image, encoder, encoding_options,
244 0 : &out_image_handle);
245 :
246 0 : heif_image_release(image);
247 :
248 : // TODO: set properties on output image
249 0 : heif_image_handle_release(out_image_handle);
250 0 : heif_encoding_options_free(encoding_options);
251 0 : heif_encoder_release(encoder);
252 :
253 0 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
254 0 : if (fp == nullptr)
255 : {
256 0 : ReportError(pszFilename, CE_Failure, CPLE_OpenFailed,
257 : "Unable to create file.");
258 0 : heif_context_free(ctx);
259 0 : return nullptr;
260 : }
261 : heif_writer writer;
262 0 : writer.writer_api_version = 1;
263 0 : writer.write = VFS_WriterCallback;
264 0 : heif_context_write(ctx, &writer, fp);
265 0 : VSIFCloseL(fp);
266 :
267 0 : heif_context_free(ctx);
268 :
269 0 : return GDALDataset::Open(pszFilename);
270 : }
271 : #endif
|