-
Notifications
You must be signed in to change notification settings - Fork 3
/
libheif.go
342 lines (304 loc) · 9.67 KB
/
libheif.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package libheif
import (
"bytes"
"fmt"
"image"
"image/jpeg"
"image/png"
"log"
"os"
"path/filepath"
"github.com/strukturag/libheif/go/heif"
)
// saveAsJpeg encodes an image to JPEG format and saves it to a file.
// The quality should be an integer between 1 and 100, inclusive, higher is better.
//
// Example:
//
// img := image.NewRGBA(image.Rect(0, 0, 100, 100))
// filename := "output.jpg"
// quality := 80
// if err := saveAsJpeg(img, filename, quality); err != nil {
// log.Fatal(err)
// }
func saveAsJpeg(img image.Image, filename string, quality int) error {
// Validate input
if img == nil {
return fmt.Errorf("image is nil")
}
if filename == "" {
return fmt.Errorf("filename is empty")
}
if quality < 1 || quality > 100 {
return fmt.Errorf("quality should be between 1 and 100")
}
// Encode the image to JPEG
var out bytes.Buffer
if err := jpeg.Encode(&out, img, &jpeg.Options{Quality: quality}); err != nil {
return fmt.Errorf("could not encode image as JPEG: %w", err)
}
// Save the JPEG data to a file
if err := os.WriteFile(filename, out.Bytes(), 0644); err != nil {
return fmt.Errorf("could not save JPEG image as %s: %w", filename, err)
}
log.Printf("Image successfully written to %s\n", filename)
return nil
}
// saveAsPng encodes an image to PNG format and saves it to a file.
//
// Example:
//
// img := image.NewRGBA(image.Rect(0, 0, 100, 100))
// filename := "output.png"
// if err := saveAsPng(img, filename); err != nil {
// log.Fatal(err)
// }
func saveAsPng(img image.Image, filename string) error {
// Validate input
if img == nil {
return fmt.Errorf("image is nil")
}
if filename == "" {
return fmt.Errorf("filename is empty")
}
// Encode the image to PNG
var out bytes.Buffer
if err := png.Encode(&out, img); err != nil {
return fmt.Errorf("could not encode image as PNG: %w", err)
}
// Save the PNG data to a file
if err := os.WriteFile(filename, out.Bytes(), 0644); err != nil {
return fmt.Errorf("could not save PNG image as %s: %w", filename, err)
}
log.Printf("Image successfully written to %s\n", filename)
return nil
}
// decodeHeifImage reads a file and decodes it into an image.
//
// This function opens the file specified by filename and decodes it into an image.
// It returns the image and the format of the image (like "jpeg", "png", "gif", "bmp" etc.).
// If an error occurs during opening the file or decoding the image, it is returned.
//
// Example:
//
// filename := "image.heic"
// img, format, err := decodeHeifImage(filename)
// if err != nil {
// log.Fatal(err)
// }
// // img is now an image.Image object and format is the format of the image.
func decodeHeifImage(filename string) (image.Image, string, error) {
// Open the file
file, err := os.Open(filename)
if err != nil {
// The file couldn't be opened
return nil, "", fmt.Errorf("could not open file %s: %v", filename, err)
}
// Ensure the file will be closed once we're done with it
defer file.Close()
// Decode the file into an image
img, format, err := image.Decode(file)
if err != nil {
// The file couldn't be decoded
return nil, "", fmt.Errorf("could not decode image: %v", err)
}
// Return the decoded image and its format
return img, format, nil
}
// convertFileToHeif opens an image file, decodes it, and then converts
// it to the HEIF format.
//
// The new HEIF image is saved to a new file, the name of which is passed
// as a parameter. If an error occurs during the process, it is returned
// to the caller.
//
// Example:
//
// srcFile := "input.jpg"
// dstFile := "output.heif"
// if err := convertFileToHeif(srcFile, dstFile); err != nil {
// log.Fatal(err)
// }
func convertFileToHeif(filename string, newFileName string) error {
// Validate input
if filename == "" || newFileName == "" {
return fmt.Errorf("input and output filenames must not be empty")
}
// Open the source file
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file %s: %v", filename, err)
}
defer file.Close()
// Decode the image and get its format
i, format, err := image.Decode(file)
if err != nil {
return fmt.Errorf("failed to decode image: %v", err)
}
// Convert the image to HEIF and save it
if err := SaveImageAsHeif(i, format, newFileName); err != nil {
return fmt.Errorf("failed to convert image to HEIF and save it: %v", err)
}
return nil
}
// ReturnImageFromHeif decodes a HEIF file and returns the resulting image.
//
// It expects the filename of a HEIF file as input. If the file cannot be
// decoded or the decoded image is not in HEIF format, an error is returned.
//
// Example:
//
// filename := "image.heic"
// img, err := returnImageFromHeif(filename)
// if err != nil {
// log.Fatal(err)
// }
func ReturnImageFromHeif(filename string) (image.Image, error) {
// Validate input
if filename == "" {
return nil, fmt.Errorf("filename must not be empty")
}
// Decode the image file
img, format, err := decodeHeifImage(filename)
if err != nil {
return nil, fmt.Errorf("could not decode the image: %s; error: %v", filename, err)
}
// Check the format of the image (heic/heif/avif)
if format != "heif" && format != "heic" && format != "avif" {
return nil, fmt.Errorf("the image %s isn't in heif format, instead it is %s", filename, format)
}
// Return the decoded image
return img, nil
}
// HeifToPng converts an image from HEIF format to PNG format.
//
// This function takes the path to the input HEIF image file and the path where
// the output PNG image file should be saved.
//
// Example:
//
// err := HeifToPng("input.heic", "output.png")
// if err != nil {
// log.Fatal(err)
// }
func HeifToPng(heifImagePath string, newPngImagePath string) error {
// Convert the HEIF image to the internal image representation.
img, err := ReturnImageFromHeif(heifImagePath)
if err != nil {
// If there was an error, return it to the caller.
return err
}
// Save the image in PNG format and return the result.
return saveAsPng(img, newPngImagePath)
}
// HeifToJpeg converts an image from HEIF format to JPEG format.
//
// This function takes the path to the input HEIF image file, the path where
// the output JPEG image file should be saved, and the quality of the output JPEG image.
//
// Example:
//
// err := HeifToJpeg("input.heic", "output.jpeg", 80)
// if err != nil {
// log.Fatal(err)
// }
func HeifToJpeg(heifImagePath string, newJpegImagePath string, quality int) error {
// Convert the HEIF image to the internal image representation.
img, err := ReturnImageFromHeif(heifImagePath)
if err != nil {
// If there was an error, return it to the caller.
return err
}
// Save the image in JPEG format with the specified quality and return the result.
return saveAsJpeg(img, newJpegImagePath, quality)
}
// ImageToHeif converts an image from JPEG or PNG format to HEIF format.
//
// This function takes the path to the input JPEG or PNG image file and the path where
// the output HEIF image file should be saved.
//
// Example:
//
// err := ImageToHeif("input.jpeg", "output.heic")
// if err != nil {
// log.Fatal(err)
// }
func ImageToHeif(jpegOrPngImagePath string, newHeifImagePath string) error {
// Convert the image to HEIF format and return the result.
return convertFileToHeif(jpegOrPngImagePath, newHeifImagePath)
}
// SaveImageAsHeif encodes an image in HEIF format and saves it to a file.
//
// The image quality is set to 100, and lossless mode is enabled. The
// original image format is printed to the console.
//
// Example:
//
// img := image.NewRGBA(image.Rect(0, 0, 100, 100))
// format := "png"
// newHeifImagePath := "output.heif"
// if err := SaveImageAsHeif(img, format, newHeifImagePath); err != nil {
// log.Fatal(err)
// }
func SaveImageAsHeif(i image.Image, format string, newHeifImagePath string) error {
// Validate input
if i == nil {
return fmt.Errorf("image is nil")
}
if format == "" {
return fmt.Errorf("format is empty")
}
if newHeifImagePath == "" {
return fmt.Errorf("output path is empty")
}
// Encode the image in HEIF format
const quality = 100
ctx, err := heif.EncodeFromImage(i, heif.CompressionHEVC, quality, heif.LosslessModeEnabled, heif.LoggingLevelFull)
if err != nil {
return fmt.Errorf("failed to HEIF encode image: %w", err)
}
// Save the HEIF data to a file
if err := ctx.WriteToFile(newHeifImagePath); err != nil {
return fmt.Errorf("failed to write to file: %w", err)
}
log.Printf("Image was %s format, Written to %s\n", format, newHeifImagePath)
return nil
}
func exampleHeifLowlevel(filename string) {
fmt.Printf("Performing lowlevel conversion of %s\n", filename)
c, err := heif.NewContext()
if err != nil {
fmt.Printf("Could not create context: %s\n", err)
return
}
if err := c.ReadFromFile(filename); err != nil {
fmt.Printf("Could not read file %s: %s\n", filename, err)
return
}
nImages := c.GetNumberOfTopLevelImages()
fmt.Printf("Number of top level images: %v\n", nImages)
ids := c.GetListOfTopLevelImageIDs()
fmt.Printf("List of top level image IDs: %#v\n", ids)
if pID, err := c.GetPrimaryImageID(); err != nil {
fmt.Printf("Could not get primary image id: %s\n", err)
} else {
fmt.Printf("Primary image: %v\n", pID)
}
handle, err := c.GetPrimaryImageHandle()
if err != nil {
fmt.Printf("Could not get primary image: %s\n", err)
return
}
fmt.Printf("Image size: %v × %v\n", handle.GetWidth(), handle.GetHeight())
img, err := handle.DecodeImage(heif.ColorspaceUndefined, heif.ChromaUndefined, nil)
if err != nil {
fmt.Printf("Could not decode image: %s\n", err)
} else if i, err := img.GetImage(); err != nil {
fmt.Printf("Could not get image: %s\n", err)
} else {
fmt.Printf("Rectangle: %v\n", i.Bounds())
ext := filepath.Ext(filename)
outFilename := filename[0:len(filename)-len(ext)] + "_lowlevel.png"
saveAsPng(i, outFilename)
}
}