"r_vsync" console var; slightly faster light tracer
[dd2d.git] / d2dimage.d
blobd3243665220aca072a8c15ec3a85d688147b75d9
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;
25 import iv.jpeg;
27 import glutils;
28 import console;
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);
249 void insertChunk (Chunk *chk) {
250 foreach (immutable idx; 0..png.chunks.length) {
251 if (cast(string)png.chunks[idx].type == "IDAT") {
252 // ok, insert before IDAT
253 png.chunks.length += 1;
254 foreach_reverse (immutable c; idx+1..png.chunks.length) png.chunks[c] = png.chunks[c-1];
255 png.chunks[idx] = *chk;
256 return;
259 png.chunks ~= *chk;
262 if (sx != 0 || sy != 0) {
264 D2DInfoChunk di;
265 di.ver = 0;
266 di.sx = sx;
267 di.sy = sy;
268 di.fixEndian;
269 auto chk = Chunk.create("dtDi", (cast(ubyte*)&di)[0..di.sizeof]);
270 //png.chunks ~= *chk;
271 insertChunk(chk);
273 // zdoom chunk
274 if (sx >= short.min && sx <= short.max && sy >= short.min && sy <= short.max) {
275 GrabChunk di;
276 di.sx = cast(short)sx;
277 di.sy = cast(short)sy;
278 di.fixEndian;
279 auto chk = Chunk.create("grAb", (cast(ubyte*)&di)[0..di.sizeof]);
280 //png.chunks ~= *chk;
281 insertChunk(chk);
284 auto fo = vfsDiskOpen(fname, "w");
285 fo.rawWriteExact(writePng(png));
288 private:
289 static align(1) struct D2DInfoChunk {
290 align(1):
291 ubyte ver; // version; 0 for now; versions should be compatible
292 int sx, sy;
294 void fixEndian () nothrow @trusted @nogc {
295 version(BigEndian) {
296 import std.bitmanip : swapEndian;
297 sx = swapEndian(sx);
298 sy = swapEndian(sy);
303 static align(1) struct GrabChunk {
304 align(1):
305 short sx, sy;
307 void fixEndian () nothrow @trusted @nogc {
308 version(LittleEndian) {
309 import std.bitmanip : swapEndian;
310 sx = swapEndian(sx);
311 sy = swapEndian(sy);
316 void loadPng (VFile fl) {
317 auto flsize = fl.size-fl.tell;
318 if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big");
319 auto data = new ubyte[](cast(uint)flsize);
320 fl.rawReadExact(data);
321 auto png = readPng(data);
322 auto ximg = imageFromPng(png).getAsTrueColorImage;
323 if (ximg is null) throw new Exception("png: wtf?!");
324 if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small");
325 mwidth = ximg.width;
326 mheight = ximg.height;
327 sx = sy = 0;
328 mimg = ximg;
329 foreach (ref chk; png.chunks) {
330 if (chk.type[] == "dtDi") {
331 // d2d info chunk
332 if (chk.size >= D2DInfoChunk.sizeof) {
333 auto di = *cast(D2DInfoChunk*)chk.payload.ptr;
334 di.fixEndian;
335 sx = di.sx;
336 sy = di.sy;
337 version(png_dump_chunks) conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
339 } else if (chk.type[] == "grAb") {
340 // zdoom info chunk
341 if (chk.size >= GrabChunk.sizeof) {
342 auto di = *cast(GrabChunk*)chk.payload.ptr;
343 di.fixEndian;
344 sx = di.sx;
345 sy = di.sy;
346 version(png_dump_chunks) conwriteln("found 'grAb' chunk! sx=", di.sx, "; sy=", di.sy);
352 void loadVga (VFile fl) {
353 //conwriteln(" loading .VGA image");
354 auto w = fl.readNum!ushort();
355 auto h = fl.readNum!ushort();
356 auto isx = fl.readNum!short();
357 auto isy = fl.readNum!short();
358 //conwriteln(" loading .VGA image; w=", w, "; h=", h, "; isx=", isx, "; isy=", isy);
359 if (w < 1 || w > 32760) throw new Exception("invalid vga image width");
360 if (h < 1 || h > 32760) throw new Exception("invalid vga image height");
361 auto data = new ubyte[](w*h);
362 fl.rawReadExact(data[]);
363 resize(w, h);
364 assert(mimg !is null);
365 assert(mimg.width == w);
366 assert(mimg.height == h);
367 assert(mwidth == w);
368 assert(mheight == h);
369 //conwriteln(" !!!");
370 sx = isx;
371 sy = isy;
372 foreach (int y; 0..height) {
373 foreach (int x; 0..width) {
374 setPixel(x, y, d2dpal.ptr[data.ptr[y*w+x]]);
379 void loadJpeg (VFile fl) {
380 auto jpg = readJpeg(fl);
381 if (jpg.width < 1 || jpg.width > 32760) throw new Exception("invalid image width");
382 if (jpg.height < 1 || jpg.height > 32760) throw new Exception("invalid image height");
383 mtex = null;
384 mwidth = jpg.width;
385 mheight = jpg.height;
386 sx = sy = 0;
387 mimg = jpg;
390 void load (VFile fl) {
391 scope(failure) fl.seek(0);
392 char[8] sign;
393 fl.seek(0);
394 fl.rawReadExact(sign[]);
395 // png?
396 if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
397 fl.seek(0);
398 loadPng(fl);
399 return;
401 // jpeg?
402 if (sign[0..2] == "\xff\xd8" && detectJpeg(fl)) {
404 fl.seek(-2, Seek.End);
405 fl.rawReadExact(sign[0..2]);
406 if (sign[0..2] == "\xff\xd9") {
407 fl.seek(0);
408 loadJpeg(fl);
409 return;
412 fl.seek(0);
413 loadJpeg(fl);
414 return;
416 // alas, this must be vga
417 fl.seek(0);
418 loadVga(fl);