summaryrefslogtreecommitdiff
path: root/src/photos/PhotoFileFormat.vala
blob: 4c69de39af7760b3bbdfe91ffff2fc7432329926 (plain)
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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

public errordomain PhotoFormatError {
    READ_ONLY
}

//
// PhotoFileFormat
//

namespace PhotoFileFormatData {
    private static PhotoFileFormat[] writeable = null;
    private static PhotoFileFormat[] image_writeable = null;
    private static PhotoFileFormat[] metadata_writeable = null;
    
    private delegate bool ApplicableTest(PhotoFileFormat format);
    
    private PhotoFileFormat[] find_applicable(ApplicableTest test) {
        PhotoFileFormat[] applicable = new PhotoFileFormat[0];
        foreach (PhotoFileFormat format in PhotoFileFormat.get_supported()) {
            if (test(format))
                applicable += format;
        }
        
        return applicable;
    }
    
    public PhotoFileFormat[] get_writeable() {
        if (writeable == null)
            writeable = find_applicable((format) => { return format.can_write(); });
        
        return writeable;
    }
    
    public static PhotoFileFormat[] get_image_writeable() {
        if (image_writeable == null)
            image_writeable = find_applicable((format) => { return format.can_write_image(); });
        
        return image_writeable;
    }
    
    public static PhotoFileFormat[] get_metadata_writeable() {
        if (metadata_writeable == null)
            metadata_writeable = find_applicable((format) => { return format.can_write_metadata(); });
        
        return metadata_writeable;
    }
}

public enum PhotoFileFormat {
    JFIF,
    RAW,
    PNG,
    TIFF,
    BMP,
    GIF,
    WEBP,
    AVIF,
    HEIF,
    JPEGXL,
    UNKNOWN;
    
    // This is currently listed in the order of detection, that is, the file is examined from
    // left to right.  (See PhotoFileInterrogator.)
    public static PhotoFileFormat[] get_supported() {
        return { JFIF, RAW, PNG, TIFF, BMP, GIF, WEBP, AVIF, HEIF, JPEGXL };
    }
    
    public static PhotoFileFormat[] get_writeable() {
        return PhotoFileFormatData.get_writeable();
    }
    
    public static PhotoFileFormat[] get_image_writeable() {
        return PhotoFileFormatData.get_image_writeable();
    }
    
    public static PhotoFileFormat[] get_metadata_writeable() {
        return PhotoFileFormatData.get_metadata_writeable();
    }
    
    public static PhotoFileFormat get_by_basename_extension(string basename) {
        string name, ext;
        disassemble_filename(basename, out name, out ext);
        
        if (is_string_empty(ext))
            return UNKNOWN;
        
        foreach (PhotoFileFormat file_format in get_supported()) {
            if (file_format.get_driver().get_properties().is_recognized_extension(ext))
                return file_format;
        }
        
        return UNKNOWN;
    }
    
    public static bool is_file_supported(File file) {
        return is_basename_supported(file.get_basename());
    }
    
    public static bool is_basename_supported(string basename) {
        string name, ext;
        disassemble_filename(basename, out name, out ext);
        
        if (is_string_empty(ext))
            return false;
        
        foreach (PhotoFileFormat format in get_supported()) {
            if (format.get_driver().get_properties().is_recognized_extension(ext))
                return true;
        }
        
        return false;
    }
    
    // Guaranteed to be writeable.
    public static PhotoFileFormat get_system_default_format() {
        return JFIF;
    }

    public static PhotoFileFormat get_by_file_extension(File file) {
        return get_by_basename_extension(file.get_basename());
    }
    
    // These values are persisted in the database.  DO NOT CHANGE THE INTEGER EQUIVALENTS.
    public int serialize() {
        switch (this) {
            case JFIF:
                return 0;
            
            case RAW:
                return 1;

            case PNG:
                return 2;
            
            case TIFF:
                return 3;

            case BMP:
                return 4;

            case GIF:
                return 5;

            case WEBP:
                return 6;

            case AVIF:
                return 7;

            case HEIF:
                return 8;

            case JPEGXL:
                return 9;

            case UNKNOWN:
            default:
                return -1;
        }
    }
    
    // These values are persisted in the database.  DO NOT CHANGE THE INTEGER EQUIVALENTS.
    public static PhotoFileFormat unserialize(int value) {
        switch (value) {
            case 0:
                return JFIF;
            
            case 1:
                return RAW;

            case 2:
                return PNG;
            
            case 3:
                return TIFF;

            case 4:
                return BMP;

            case 5:
                return GIF;
                            
            case 6:
                return WEBP;

            case 7:
                return AVIF;

            case 8:
                return HEIF;

            case 9:
                return JPEGXL;

            default:
                return UNKNOWN;
        }
    }

    public static PhotoFileFormat from_gphoto_type(string type) {
        switch (type) {
            case GPhoto.MIME.JPEG:
                return PhotoFileFormat.JFIF;
            
            case GPhoto.MIME.RAW:
            case GPhoto.MIME.CRW:
                return PhotoFileFormat.RAW;
            
            case GPhoto.MIME.PNG:
                return PhotoFileFormat.PNG;
            
            case GPhoto.MIME.TIFF:
                return PhotoFileFormat.TIFF;

            case GPhoto.MIME.BMP:
                return PhotoFileFormat.BMP;

            // GPhoto does not have GIF

            default:
                // check file extension against those we support
                return PhotoFileFormat.UNKNOWN;
        }
    }
    
    // Converts GDK's pixbuf library's name to a PhotoFileFormat
    public static PhotoFileFormat from_pixbuf_name(string name) {
        switch (name) {
            case "jpeg":
                return PhotoFileFormat.JFIF;
            
            case "png":
                return PhotoFileFormat.PNG;
            
            case "tiff":
                return PhotoFileFormat.TIFF;
            
            case "bmp":
                return PhotoFileFormat.BMP;

            case "gif":
                return PhotoFileFormat.GIF;

            case "heif/avif":
            case "avif":
                return PhotoFileFormat.AVIF;

            case "heif":
                return PhotoFileFormat.HEIF;

            case "jxl":
                return PhotoFileFormat.JPEGXL;

            default:
                return PhotoFileFormat.UNKNOWN;
        }
    }
    
    public void init() {
        switch (this) {
            case JFIF:
                JfifFileFormatDriver.init();
                break;
            
            case RAW:
                RawFileFormatDriver.init();
                break;
            
            case PNG:
                PngFileFormatDriver.init();
                break;
            
            case TIFF:
                Photos.TiffFileFormatDriver.init();
                break;
            
            case BMP:
                Photos.BmpFileFormatDriver.init();
                break;

            case GIF:
                Photos.GifFileFormatDriver.init();
                break;

            case WEBP:
                Photos.WebpFileFormatDriver.init();
                break;

            case AVIF:
                AvifFileFormatDriver.init();
                break;

            case HEIF:
                HeifFileFormatDriver.init();
                break;

            case JPEGXL:
                JpegXLFileFormatDriver.init();
                break;

            default:
                error("Unsupported file format %s", this.to_string());
        }
    }
    
    private PhotoFileFormatDriver get_driver() {
        switch (this) {
            case JFIF:
                return JfifFileFormatDriver.get_instance();
            
            case RAW:
                return RawFileFormatDriver.get_instance();
            
            case PNG:
                return PngFileFormatDriver.get_instance();
            
            case TIFF:
                return Photos.TiffFileFormatDriver.get_instance();
            
            case BMP:
                return Photos.BmpFileFormatDriver.get_instance();

            case GIF:
                return Photos.GifFileFormatDriver.get_instance();

            case WEBP:
                return Photos.WebpFileFormatDriver.get_instance();

            case AVIF:
                return AvifFileFormatDriver.get_instance();

            case HEIF:
                return HeifFileFormatDriver.get_instance();

            case JPEGXL:
                return JpegXLFileFormatDriver.get_instance();

            default:
                error("Unsupported file format %s", this.to_string());
        }
    }
    
    public PhotoFileFormatProperties get_properties() {
        return get_driver().get_properties();
    }
    
    // Supplied with a name, returns the name with the file format's default extension.
    public string get_default_basename(string name) {
        return "%s.%s".printf(name, get_properties().get_default_extension());
    }
    
    public PhotoFileReader create_reader(string filepath) {
        return get_driver().create_reader(filepath);
    }
    
    // This means the image and its metadata are writeable.
    public bool can_write() {
        return can_write_image() && can_write_metadata();
    }
    
    public bool can_write_image() {
        return get_driver().can_write_image();
    }
    
    public bool can_write_metadata() {
        return get_driver().can_write_metadata();
    }
    
    public PhotoFileWriter create_writer(string filepath) throws PhotoFormatError {
        PhotoFileWriter writer = get_driver().create_writer(filepath);
        if (writer == null)
            throw new PhotoFormatError.READ_ONLY("File format %s is read-only", this.to_string());
        
        return writer;
    }
    
    public PhotoFileMetadataWriter create_metadata_writer(string filepath) throws PhotoFormatError {
        PhotoFileMetadataWriter writer = get_driver().create_metadata_writer(filepath);
        if (writer == null)
            throw new PhotoFormatError.READ_ONLY("File format %s metadata is read-only", this.to_string());
        
        return writer;
    }
    
    public PhotoFileSniffer create_sniffer(File file, PhotoFileSniffer.Options options) {
        return get_driver().create_sniffer(file, options);
    }
    
    public PhotoMetadata create_metadata() {
        return get_driver().create_metadata();
    }
    
    public string get_default_mime_type() {
        return get_driver().get_properties().get_default_mime_type();
    }
    
    public string[] get_mime_types() {
        return get_driver().get_properties().get_mime_types();
    }
    
    public static string[] get_editable_mime_types() {
        string[] mime_types = {};
        
        foreach (PhotoFileFormat file_format in PhotoFileFormat.get_supported()) {
            foreach (string mime_type in file_format.get_mime_types())
                mime_types += mime_type;
        }
        
        return mime_types;
    }
}

//
// PhotoFileFormatDriver
//
// Each supported file format is expected to have a PhotoFileFormatDriver that returns all possible
// resources that are needed to operate on file of its particular type.  It's expected that each
// format subsystem will only create and cache a single instance of this driver, although it's
// not required.
//
// Like the other elements in the PhotoFileFormat family, this class should be thread-safe.
//

public abstract class PhotoFileFormatDriver {
    public abstract PhotoFileFormatProperties get_properties();
    
    public abstract PhotoFileReader create_reader(string filepath);
    
    public abstract PhotoMetadata create_metadata();
    
    public abstract bool can_write_image();
    
    public abstract bool can_write_metadata();
    
    public abstract PhotoFileWriter? create_writer(string filepath);
    
    public abstract PhotoFileMetadataWriter? create_metadata_writer(string filepath);
    
    public abstract PhotoFileSniffer create_sniffer(File file, PhotoFileSniffer.Options options);
}

//
// PhotoFileFormatProperties
//
// Although each PhotoFileFormatProperties is expected to be largely static and immutable, these
// classes should be thread-safe.
//

public enum PhotoFileFormatFlags {
    NONE =                  0x00000000,
}

public abstract class PhotoFileFormatProperties {
    public abstract PhotoFileFormat get_file_format();
    
    public abstract PhotoFileFormatFlags get_flags();
    
    // Default implementation will search for ext in get_known_extensions(), assuming they are
    // all stored in lowercase.
    public virtual bool is_recognized_extension(string ext) {
        return is_in_ci_array(ext, get_known_extensions());
    }
    
    public abstract string get_default_extension();
    
    public abstract string[] get_known_extensions();
    
    public abstract string get_default_mime_type();
    
    public abstract string[] get_mime_types();

    // returns the user-visible name of the file format -- this name is used in user interface
    // strings whenever the file format needs to named. This name is not the same as the format
    // enum value converted to a string. The format enum value is meaningful to developers and is
    // constant across languages (e.g. "JFIF", "TGA") whereas the user-visible name is translatable
    // and is meaningful to users (e.g. "JPEG", "Truevision TARGA")
    public abstract string get_user_visible_name();
    
    // Takes a given file and returns one with the file format's default extension, unless it
    // already has one of the format's known extensions
    public File convert_file_extension(File file) {
        string name, ext;
        disassemble_filename(file.get_basename(), out name, out ext);
        if (ext != null && is_recognized_extension(ext))
            return file;
        
        return file.get_parent().get_child("%s.%s".printf(name, get_default_extension()));
    }
}