really compiles! ;-)
[dd2d.git] / d2dimage.d
blob36fc78b98eeb18c1848674b0dac3c13f0eb17b47
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.cmdcon;
25 import iv.glbinds;
26 import iv.jpegd;
28 import glutils;
29 import wadarc;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public __gshared Color[256] d2dpal;
36 public void loadD2DPalette () {
37 ubyte[768] vgapal;
39 auto fl = VFile("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, setExtension;
66 if (name.extension == ".vga") {
67 static immutable string[4] extList = [".vga", ".png", ".jpg", ".jpeg"];
68 foreach (string ext; extList) {
69 try {
70 auto nn = name.setExtension(ext);
71 auto fl = VFile(nn);
72 conwriteln("loading image '", nn, "'");
73 load(fl);
74 return;
75 } catch (Exception e) {
76 //conwriteln("ERROR: ", e.msg);
80 auto fl = VFile(name);
81 conwriteln("loading image '", name, "'");
82 load(fl);
85 this (int awdt, int ahgt) nothrow @trusted {
86 assert(awdt >= 0 && ahgt >= 0);
87 sx = sy = 0;
88 mwidth = awdt;
89 mheight = ahgt;
90 if (awdt > 0 && ahgt > 0) {
91 try {
92 mimg = new TrueColorImage(awdt, ahgt);
93 } catch (Exception e) {
94 assert(0, e.toString);
96 mimg.imageData.bytes[] = 0;
100 /// will not clone texture!
101 D2DImage clone () const nothrow @trusted {
102 auto res = new D2DImage();
103 res.sx = sx;
104 res.sy = sy;
105 res.mwidth = mwidth;
106 res.mheight = mheight;
107 if (valid) {
108 try {
109 res.mimg = new TrueColorImage(mwidth, mheight);
110 } catch (Exception e) {
111 assert(0, e.toString);
113 res.mimg.imageData.bytes[] = mimg.imageData.bytes[];
115 return res;
118 void resize (int awdt, int ahgt) nothrow @trusted {
119 assert(awdt >= 0 && ahgt >= 0);
120 if (mwidth != awdt || mheight != ahgt) {
121 mtex = null;
122 mwidth = awdt;
123 mheight = ahgt;
124 sx = sy = 0;
125 if (awdt > 0 && ahgt > 0) {
126 try {
127 mimg = new TrueColorImage(awdt, ahgt);
128 } catch (Exception e) {
129 assert(0, e.toString);
131 mimg.imageData.bytes[] = 0;
134 if (mwidth <= 0 || mheight <= 0) mimg = null;
137 void clear () {
138 //if (mtex !is null) mtex.clear;
139 //if (mimg !is null) mimg.clear;
140 mtex = null;
141 mimg = null;
142 mwidth = mheight = 0;
143 sx = sy = 0;
146 void removeOffset () { sx = sy = 0; }
148 @property const pure nothrow @safe @nogc {
149 bool valid () { pragma(inline, true); return (mimg !is null && mwidth > 0 && mheight > 0); }
150 int width () { pragma(inline, true); return mwidth; }
151 int height () { pragma(inline, true); return mheight; }
153 // DO NOT RESIZE!
154 @property TrueColorImage img () pure nothrow @safe @nogc { pragma(inline, true); return mimg; }
156 Color opIndex (usize y, usize x) const /*pure*/ nothrow @safe @nogc { pragma(inline, true); return getPixel(x, y); }
157 void opIndex (Color clr, usize y, usize x) nothrow @safe @nogc { pragma(inline, true); setPixel(x, y, clr); }
158 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); }
160 Color getPixel (int x, int y) const /*pure*/ nothrow @trusted @nogc {
161 pragma(inline, true);
162 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));
165 void setPixel (int x, int y, Color clr) nothrow @trusted @nogc {
166 pragma(inline, true);
167 if (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight) (cast(Color*)mimg.imageData.bytes.ptr)[y*mwidth+x] = clr;
170 void putPixel (int x, int y, Color clr) nothrow @trusted @nogc {
171 pragma(inline, true);
172 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;
175 void toBlackAndWhite () {
176 if (!valid) return;
177 foreach (int y; 0..mheight) {
178 foreach (int x; 0..mwidth) {
179 Color clr = getPixel(x, y);
180 int i = cast(int)(0.2126*clr.r+0.7152*clr.g+0.0722*clr.b);
181 if (i > 255) i = 255; // just in case
182 setPixel(x, y, Color(i&0xff, i&0xff, i&0xff, clr.a));
187 void toBlackAndWhiteChan(string chan) () {
188 static assert(chan == "red" || chan == "r" || chan == "green" || chan == "g" || chan == "blue" || chan == "b", "invalid channel");
189 if (!valid) return;
190 foreach (int y; 0..mheight) {
191 foreach (int x; 0..mwidth) {
192 Color clr = getPixel(x, y);
193 static if (chan == "red" || chan == "r") ubyte i = clr.r;
194 else static if (chan == "green" || chan == "g") ubyte i = clr.g;
195 else static if (chan == "blue" || chan == "b") ubyte i = clr.b;
196 else static assert(0, "wtf?!");
197 setPixel(x, y, Color(i, i, i, clr.a));
202 D2DImage blackAndWhite () {
203 auto res = this.clone();
204 res.toBlackAndWhite;
205 return res;
208 D2DImage blackAndWhiteChan(string chan) () {
209 auto res = this.clone();
210 res.toBlackAndWhiteChan!chan;
211 return res;
214 /// mirror image horizontally
215 void mirror () {
216 if (!valid) return;
217 foreach (int y; 0..height) {
218 foreach (int x; 0..width/2) {
219 int mx = width-x-1;
220 auto c0 = getPixel(x, y);
221 auto c1 = getPixel(mx, y);
222 setPixel(x, y, c1);
223 setPixel(mx, y, c0);
226 sx = width-sx-1;
229 void createTex () {
230 if (mtex is null && valid) mtex = new Texture(mimg, Texture.Option.Nearest);
233 void updateTex () {
234 if (valid) {
235 if (mtex is null) mtex = new Texture(mimg, Texture.Option.Nearest); else mtex.setFromImage(mimg);
239 GLuint asTex () {
240 if (!valid) return 0;
241 if (mtex is null) createTex();
242 return (mtex !is null ? mtex.id : 0);
245 void savePng (string fname) {
246 if (!valid) throw new Exception("can't save empty image");
247 auto png = pngFromImage(mimg);
250 void insertChunk (Chunk *chk) {
251 foreach (immutable idx; 0..png.chunks.length) {
252 if (cast(string)png.chunks[idx].type == "IDAT") {
253 // ok, insert before IDAT
254 png.chunks.length += 1;
255 foreach_reverse (immutable c; idx+1..png.chunks.length) png.chunks[c] = png.chunks[c-1];
256 png.chunks[idx] = *chk;
257 return;
260 png.chunks ~= *chk;
264 if (sx != 0 || sy != 0) {
266 D2DInfoChunk di;
267 di.ver = 0;
268 di.sx = sx;
269 di.sy = sy;
270 di.fixEndian;
271 auto chk = Chunk.create("dtDi", (cast(ubyte*)&di)[0..di.sizeof]);
272 //png.chunks ~= *chk;
273 png.insertChunk(chk);
275 // zdoom chunk
276 if (sx >= short.min && sx <= short.max && sy >= short.min && sy <= short.max) {
277 GrabChunk di;
278 di.sx = cast(short)sx;
279 di.sy = cast(short)sy;
280 di.fixEndian;
281 auto chk = Chunk.create("grAb", (cast(ubyte*)&di)[0..di.sizeof]);
282 //png.chunks ~= *chk;
283 png.insertChunk(chk);
286 auto fo = vfsDiskOpen(fname, "w");
287 fo.rawWriteExact(writePng(png));
290 private:
291 static align(1) struct D2DInfoChunk {
292 align(1):
293 ubyte ver; // version; 0 for now; versions should be compatible
294 int sx, sy;
296 void fixEndian () nothrow @trusted @nogc {
297 version(BigEndian) {
298 import std.bitmanip : swapEndian;
299 sx = swapEndian(sx);
300 sy = swapEndian(sy);
305 static align(1) struct GrabChunk {
306 align(1):
307 short sx, sy;
309 void fixEndian () nothrow @trusted @nogc {
310 version(LittleEndian) {
311 import std.bitmanip : swapEndian;
312 sx = swapEndian(sx);
313 sy = swapEndian(sy);
318 void loadPng (VFile fl) {
319 auto flsize = fl.size-fl.tell;
320 if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big");
321 auto data = new ubyte[](cast(uint)flsize);
322 fl.rawReadExact(data);
323 auto png = readPng(data);
324 auto ximg = imageFromPng(png).getAsTrueColorImage;
325 if (ximg is null) throw new Exception("png: wtf?!");
326 if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small");
327 mwidth = ximg.width;
328 mheight = ximg.height;
329 sx = sy = 0;
330 mimg = ximg;
331 foreach (ref chk; png.chunks) {
332 if (chk.type[] == "dtDi") {
333 // d2d info chunk
334 if (chk.size >= D2DInfoChunk.sizeof) {
335 auto di = *cast(D2DInfoChunk*)chk.payload.ptr;
336 di.fixEndian;
337 sx = di.sx;
338 sy = di.sy;
339 version(png_dump_chunks) conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
341 } else if (chk.type[] == "grAb") {
342 // zdoom info chunk
343 if (chk.size >= GrabChunk.sizeof) {
344 auto di = *cast(GrabChunk*)chk.payload.ptr;
345 di.fixEndian;
346 sx = di.sx;
347 sy = di.sy;
348 version(png_dump_chunks) conwriteln("found 'grAb' chunk! sx=", di.sx, "; sy=", di.sy);
354 void loadVga (VFile fl) {
355 //conwriteln(" loading .VGA image");
356 auto w = fl.readNum!ushort();
357 auto h = fl.readNum!ushort();
358 auto isx = fl.readNum!short();
359 auto isy = fl.readNum!short();
360 //conwriteln(" loading .VGA image; w=", w, "; h=", h, "; isx=", isx, "; isy=", isy);
361 if (w < 1 || w > 32760) throw new Exception("invalid vga image width");
362 if (h < 1 || h > 32760) throw new Exception("invalid vga image height");
363 auto data = new ubyte[](w*h);
364 fl.rawReadExact(data[]);
365 resize(w, h);
366 assert(mimg !is null);
367 assert(mimg.width == w);
368 assert(mimg.height == h);
369 assert(mwidth == w);
370 assert(mheight == h);
371 //conwriteln(" !!!");
372 sx = isx;
373 sy = isy;
374 foreach (int y; 0..height) {
375 foreach (int x; 0..width) {
376 setPixel(x, y, d2dpal.ptr[data.ptr[y*w+x]]);
381 void loadJpeg (VFile fl) {
382 auto jpg = readJpeg(fl);
383 if (jpg.width < 1 || jpg.width > 32760) throw new Exception("invalid image width");
384 if (jpg.height < 1 || jpg.height > 32760) throw new Exception("invalid image height");
385 mtex = null;
386 mwidth = jpg.width;
387 mheight = jpg.height;
388 sx = sy = 0;
389 mimg = jpg.getAsTrueColorImage;
392 void load (VFile fl) {
393 scope(failure) fl.seek(0);
394 char[8] sign;
395 fl.seek(0);
396 fl.rawReadExact(sign[]);
397 // png?
398 if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
399 fl.seek(0);
400 loadPng(fl);
401 return;
403 // jpeg?
404 int width, height, actual_comps;
405 if (sign[0..2] == "\xff\xd8" && detectJpeg(fl, width, height, actual_comps)) {
407 fl.seek(-2, Seek.End);
408 fl.rawReadExact(sign[0..2]);
409 if (sign[0..2] == "\xff\xd9") {
410 fl.seek(0);
411 loadJpeg(fl);
412 return;
415 fl.seek(0);
416 loadJpeg(fl);
417 return;
419 // alas, this must be vga
420 fl.seek(0);
421 loadVga(fl);