optimized console png images
[dd2d.git] / d2dimage.d
blobda0c981d7386e4fbdd48119e4a63b33a100567a2
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module d2dimage is aliced;
19 private:
21 import arsd.color;
22 import arsd.png;
23 import iv.vfs;
24 import iv.glbinds;
26 import glutils;
27 import console;
28 import wadarc;
29 import jpeg;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public __gshared Color[256] d2dpal;
36 public void loadD2DPalette () {
37 ubyte[768] vgapal;
39 auto fl = openFile("playpal.pal");
40 fl.rawReadExact(vgapal[]);
41 foreach (ref ubyte b; vgapal) if (b > 63) b = 63; // just in case
43 foreach (immutable idx; 0..256) {
44 d2dpal[idx].r = cast(ubyte)(vgapal[idx*3+0]*255/63);
45 d2dpal[idx].g = cast(ubyte)(vgapal[idx*3+1]*255/63);
46 d2dpal[idx].b = cast(ubyte)(vgapal[idx*3+2]*255/63);
47 d2dpal[idx].a = 255;
49 // color 0 is transparent
50 d2dpal[0].asUint = 0;
54 // ////////////////////////////////////////////////////////////////////////// //
55 public final class D2DImage {
56 public:
57 int sx, sy;
58 int mwidth, mheight;
59 private TrueColorImage mimg;
60 private Texture mtex;
62 private this () pure nothrow @safe {}
64 this (string name) {
65 import std.path : extension;
66 auto fl = openFile(name);
67 conwriteln("loading image '", name, "'");
68 load(fl);
71 this (int awdt, int ahgt) nothrow @trusted {
72 assert(awdt >= 0 && ahgt >= 0);
73 sx = sy = 0;
74 mwidth = awdt;
75 mheight = ahgt;
76 if (awdt > 0 && ahgt > 0) {
77 try {
78 mimg = new TrueColorImage(awdt, ahgt);
79 } catch (Exception e) {
80 assert(0, e.toString);
82 mimg.imageData.bytes[] = 0;
86 /// will not clone texture!
87 D2DImage clone () const nothrow @trusted {
88 auto res = new D2DImage();
89 res.sx = sx;
90 res.sy = sy;
91 res.mwidth = mwidth;
92 res.mheight = mheight;
93 if (valid) {
94 try {
95 res.mimg = new TrueColorImage(mwidth, mheight);
96 } catch (Exception e) {
97 assert(0, e.toString);
99 res.mimg.imageData.bytes[] = mimg.imageData.bytes[];
101 return res;
104 void resize (int awdt, int ahgt) nothrow @trusted {
105 assert(awdt >= 0 && ahgt >= 0);
106 if (mwidth != awdt || mheight != ahgt) {
107 mtex = null;
108 mwidth = awdt;
109 mheight = ahgt;
110 sx = sy = 0;
111 if (awdt > 0 && ahgt > 0) {
112 try {
113 mimg = new TrueColorImage(awdt, ahgt);
114 } catch (Exception e) {
115 assert(0, e.toString);
117 mimg.imageData.bytes[] = 0;
120 if (mwidth <= 0 || mheight <= 0) mimg = null;
123 void clear () {
124 //if (mtex !is null) mtex.clear;
125 //if (mimg !is null) mimg.clear;
126 mtex = null;
127 mimg = null;
128 mwidth = mheight = 0;
129 sx = sy = 0;
132 void removeOffset () { sx = sy = 0; }
134 @property const pure nothrow @safe @nogc {
135 bool valid () { pragma(inline, true); return (mimg !is null && mwidth > 0 && mheight > 0); }
136 int width () { pragma(inline, true); return mwidth; }
137 int height () { pragma(inline, true); return mheight; }
139 // DO NOT RESIZE!
140 @property TrueColorImage img () pure nothrow @safe @nogc { pragma(inline, true); return mimg; }
142 Color opIndex (usize y, usize x) const /*pure*/ nothrow @safe @nogc { pragma(inline, true); return getPixel(x, y); }
143 void opIndex (Color clr, usize y, usize x) nothrow @safe @nogc { pragma(inline, true); setPixel(x, y, clr); }
144 void opIndex (Color clr, usize y, usize x, bool ignoreTransparent) nothrow @safe @nogc { pragma(inline, true); if (!ignoreTransparent || clr.a != 0) setPixel(x, y, clr); }
146 Color getPixel (int x, int y) const /*pure*/ nothrow @trusted @nogc {
147 pragma(inline, true);
148 return (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight ? (cast(const(Color*))mimg.imageData.bytes.ptr)[y*mwidth+x] : Color(0, 0, 0, 0));
151 void setPixel (int x, int y, Color clr) nothrow @trusted @nogc {
152 pragma(inline, true);
153 if (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight) (cast(Color*)mimg.imageData.bytes.ptr)[y*mwidth+x] = clr;
156 void putPixel (int x, int y, Color clr) nothrow @trusted @nogc {
157 pragma(inline, true);
158 if (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight && clr.a != 0) (cast(Color*)mimg.imageData.bytes.ptr)[y*mwidth+x] = clr;
161 void toBlackAndWhite () {
162 if (!valid) return;
163 foreach (int y; 0..mheight) {
164 foreach (int x; 0..mwidth) {
165 Color clr = getPixel(x, y);
166 int i = cast(int)(0.2126*clr.r+0.7152*clr.g+0.0722*clr.b);
167 if (i > 255) i = 255; // just in case
168 setPixel(x, y, Color(i&0xff, i&0xff, i&0xff, clr.a));
173 void toBlackAndWhiteChan(string chan) () {
174 static assert(chan == "red" || chan == "r" || chan == "green" || chan == "g" || chan == "blue" || chan == "b", "invalid channel");
175 if (!valid) return;
176 foreach (int y; 0..mheight) {
177 foreach (int x; 0..mwidth) {
178 Color clr = getPixel(x, y);
179 static if (chan == "red" || chan == "r") ubyte i = clr.r;
180 else static if (chan == "green" || chan == "g") ubyte i = clr.g;
181 else static if (chan == "blue" || chan == "b") ubyte i = clr.b;
182 else static assert(0, "wtf?!");
183 setPixel(x, y, Color(i, i, i, clr.a));
188 D2DImage blackAndWhite () {
189 auto res = this.clone();
190 res.toBlackAndWhite;
191 return res;
194 D2DImage blackAndWhiteChan(string chan) () {
195 auto res = this.clone();
196 res.toBlackAndWhiteChan!chan;
197 return res;
200 /// mirror image horizontally
201 void mirror () {
202 if (!valid) return;
203 foreach (int y; 0..height) {
204 foreach (int x; 0..width/2) {
205 int mx = width-x-1;
206 auto c0 = getPixel(x, y);
207 auto c1 = getPixel(mx, y);
208 setPixel(x, y, c1);
209 setPixel(mx, y, c0);
212 sx = width-sx-1;
215 void createTex () {
216 if (mtex is null && valid) mtex = new Texture(mimg, Texture.Option.Nearest);
219 void updateTex () {
220 if (valid) {
221 if (mtex is null) mtex = new Texture(mimg, Texture.Option.Nearest); else mtex.setFromImage(mimg);
225 GLuint asTex () {
226 if (!valid) return 0;
227 if (mtex is null) createTex();
228 return (mtex !is null ? mtex.id : 0);
231 void savePng (string fname) {
232 if (!valid) throw new Exception("can't save empty image");
233 auto png = pngFromImage(mimg);
235 D2DInfoChunk di;
236 di.ver = 0;
237 di.sx = sx;
238 di.sy = sy;
239 di.fixEndian;
240 auto chk = Chunk.create("dtDi", (cast(ubyte*)&di)[0..di.sizeof]);
241 png.chunks ~= *chk;
243 // zdoom chunk
244 if (sx >= short.min && sx <= short.max && sy >= short.min && sy <= short.max) {
245 GrabChunk di;
246 di.sx = cast(short)sx;
247 di.sy = cast(short)sy;
248 di.fixEndian;
249 auto chk = Chunk.create("grAb", (cast(ubyte*)&di)[0..di.sizeof]);
250 png.chunks ~= *chk;
252 auto fo = vfsDiskOpen(fname, "w");
253 fo.rawWriteExact(writePng(png));
256 private:
257 static align(1) struct D2DInfoChunk {
258 align(1):
259 ubyte ver; // version; 0 for now; versions should be compatible
260 int sx, sy;
262 void fixEndian () nothrow @trusted @nogc {
263 version(BigEndian) {
264 import std.bitmanip : swapEndian;
265 sx = swapEndian(sx);
266 sy = swapEndian(sy);
271 static align(1) struct GrabChunk {
272 align(1):
273 short sx, sy;
275 void fixEndian () nothrow @trusted @nogc {
276 version(LittleEndian) {
277 import std.bitmanip : swapEndian;
278 sx = swapEndian(sx);
279 sy = swapEndian(sy);
284 void loadPng (VFile fl) {
285 auto flsize = fl.size-fl.tell;
286 if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big");
287 auto data = new ubyte[](cast(uint)flsize);
288 fl.rawReadExact(data);
289 auto png = readPng(data);
290 auto ximg = imageFromPng(png).getAsTrueColorImage;
291 if (ximg is null) throw new Exception("png: wtf?!");
292 if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small");
293 mwidth = ximg.width;
294 mheight = ximg.height;
295 sx = sy = 0;
296 mimg = ximg;
297 foreach (ref chk; png.chunks) {
298 if (chk.type[] == "dtDi") {
299 // d2d info chunk
300 if (chk.size >= D2DInfoChunk.sizeof) {
301 auto di = *cast(D2DInfoChunk*)chk.payload.ptr;
302 di.fixEndian;
303 sx = di.sx;
304 sy = di.sy;
305 //conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
307 } else if (chk.type[] == "grAb") {
308 // zdoom info chunk
309 if (chk.size >= GrabChunk.sizeof) {
310 auto di = *cast(GrabChunk*)chk.payload.ptr;
311 di.fixEndian;
312 sx = di.sx;
313 sy = di.sy;
314 //conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
320 void loadVga (VFile fl) {
321 //conwriteln(" loading .VGA image");
322 auto w = fl.readNum!ushort();
323 auto h = fl.readNum!ushort();
324 auto isx = fl.readNum!short();
325 auto isy = fl.readNum!short();
326 //conwriteln(" loading .VGA image; w=", w, "; h=", h, "; isx=", isx, "; isy=", isy);
327 if (w < 1 || w > 32760) throw new Exception("invalid vga image width");
328 if (h < 1 || h > 32760) throw new Exception("invalid vga image height");
329 auto data = new ubyte[](w*h);
330 fl.rawReadExact(data[]);
331 resize(w, h);
332 assert(mimg !is null);
333 assert(mimg.width == w);
334 assert(mimg.height == h);
335 assert(mwidth == w);
336 assert(mheight == h);
337 //conwriteln(" !!!");
338 sx = isx;
339 sy = isy;
340 foreach (int y; 0..height) {
341 foreach (int x; 0..width) {
342 setPixel(x, y, d2dpal.ptr[data.ptr[y*w+x]]);
347 void loadJpeg (VFile fl) {
348 auto jpg = new JpegDecoder(fl, JpegDecoder.Upsampling.BILINEAR);
349 if (jpg.image.width < 1 || jpg.image.width > 32760) throw new Exception("invalid image width");
350 if (jpg.image.height < 1 || jpg.image.height > 32760) throw new Exception("invalid image height");
351 mtex = null;
352 mwidth = jpg.image.width;
353 mheight = jpg.image.height;
354 sx = sy = 0;
355 mimg = jpg.image;
358 void load (VFile fl) {
359 scope(failure) fl.seek(0);
360 char[8] sign;
361 fl.seek(0);
362 fl.rawReadExact(sign[]);
363 fl.seek(0);
364 // png?
365 if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
366 loadPng(fl);
367 return;
369 // jpeg?
370 if (sign[0..2] == "\xff\xd8") {
371 fl.seek(-2, Seek.End);
372 fl.rawReadExact(sign[0..2]);
373 fl.seek(0);
374 if (sign[0..2] == "\xff\xd9") {
375 loadJpeg(fl);
376 return;
379 // alas, this must be vga
380 loadVga(fl);