1 /** 2 TGA support. 3 4 Copyright: Copyright Guillaume Piolat 2023 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module gamut.plugins.tga; 8 9 nothrow @nogc @safe: 10 11 import core.stdc.stdlib: malloc, free, realloc; 12 import gamut.types; 13 import gamut.io; 14 import gamut.plugin; 15 import gamut.image; 16 import gamut.internals.errors; 17 import gamut.internals.types; 18 19 version(decodeTGA) import gamut.codecs.tga; 20 else version(encodeTGA) import gamut.codecs.tga; 21 22 ImageFormatPlugin makeTGAPlugin() 23 { 24 ImageFormatPlugin p; 25 p.format = "TGA"; 26 p.extensionList = "tga"; 27 p.mimeTypes = "image/tga"; 28 version(decodeTGA) 29 p.loadProc = &loadTGA; 30 else 31 p.loadProc = null; 32 version(encodeTGA) 33 p.saveProc = &saveTGA; 34 else 35 p.saveProc = null; 36 p.detectProc = &detectTGA; 37 return p; 38 } 39 40 41 version(decodeTGA) 42 void loadTGA(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 43 { 44 TGADecoder decoder; 45 if (!decoder.initialize(io, handle)) 46 { 47 image.error(kStrImageDecodingFailed); 48 return; 49 } 50 51 if (!decoder.getImageInfo()) 52 { 53 image.error(kStrImageDecodingFailed); 54 return; 55 } 56 57 if (!imageIsValidSize(1, decoder._width, decoder._height)) 58 { 59 image.error(kStrImageTooLarge); 60 return; 61 } 62 63 // Allocate space for loaded image, following constraints. 64 int decodedComponents; 65 ubyte* decoded = decoder.decodeImage(&decodedComponents); 66 67 if (decoded is null) 68 { 69 image.error(kStrImageDecodingFailed); 70 return; 71 } 72 73 if (decodedComponents == 1) 74 image._type = PixelType.l8; 75 else if (decodedComponents == 2) 76 image._type = PixelType.la8; 77 else if (decodedComponents == 3) 78 image._type = PixelType.rgb8; 79 else if (decodedComponents == 4) 80 image._type = PixelType.rgba8; 81 82 image._width = decoder._width; 83 image._height = decoder._height; 84 image._allocArea = decoded; 85 image._data = decoded; 86 image._pitch = decoder._width * decodedComponents; 87 image._pixelAspectRatio = GAMUT_UNKNOWN_ASPECT_RATIO; 88 image._resolutionY = GAMUT_UNKNOWN_RESOLUTION; 89 image._layoutConstraints = LAYOUT_DEFAULT; 90 image._layerCount = 1; 91 image._layerOffset = 0; 92 93 // Convert to target type and constraints 94 image.convertTo(applyLoadFlags(image._type, flags), cast(LayoutConstraints) flags); 95 } 96 97 bool detectTGA(IOStream *io, IOHandle handle) @trusted 98 { 99 version(decodeTGA) 100 { 101 // save I/O cursor 102 c_long offset = io.tell(handle); 103 104 bool res = false; 105 { 106 TGADecoder decoder; 107 if (decoder.initialize(io, handle)) 108 { 109 res = decoder.getImageInfo(); 110 } 111 } 112 113 // restore I/O cursor 114 if (!io.seekAbsolute(handle, offset)) 115 { 116 // TODO: that rare error should propagate somehow, 117 // we couldn't reset the cursor hence more detection will fail. 118 return false; 119 } 120 return res; 121 } 122 else 123 { 124 return false; 125 } 126 } 127 128 version(encodeTGA) 129 bool saveTGA(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 130 { 131 if (page != 0) 132 return false; 133 134 TGAEncoder encoder; 135 136 bool enableRLE = true; // No real reason not to enable RLE. 137 if (!encoder.initialize(io, handle, image._type, image._width, image._height, enableRLE)) 138 { 139 return false; 140 } 141 142 // Encode scanline one by one, to allow conversion on write. 143 for (int y = image._height - 1; y >= 0; --y) 144 { 145 if (!encoder.encodeScanline(image.scanptr(y))) 146 return false; 147 } 148 149 return true; 150 }