simple command history
[dd2d.git] / d2dimage.d
blobc28243e2e52738675ae9da529914d1f358b821f7
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;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public __gshared Color[256] d2dpal;
35 public void loadD2DPalette () {
36 ubyte[768] vgapal;
38 auto fl = openFile("playpal.pal");
39 fl.rawReadExact(vgapal[]);
40 foreach (ref ubyte b; vgapal) if (b > 63) b = 63; // just in case
42 foreach (immutable idx; 0..256) {
43 d2dpal[idx].r = cast(ubyte)(vgapal[idx*3+0]*255/63);
44 d2dpal[idx].g = cast(ubyte)(vgapal[idx*3+1]*255/63);
45 d2dpal[idx].b = cast(ubyte)(vgapal[idx*3+2]*255/63);
46 d2dpal[idx].a = 255;
48 // color 0 is transparent
49 d2dpal[0].asUint = 0;
53 // ////////////////////////////////////////////////////////////////////////// //
54 public final class D2DImage {
55 public:
56 int sx, sy;
57 int mwidth, mheight;
58 private TrueColorImage mimg;
59 private Texture mtex;
61 private this () pure nothrow @safe {}
63 this (string name) {
64 import std.path : extension;
65 auto fl = openFile(name);
66 conwriteln("loading image '", name, "'");
67 load(fl);
70 this (int awdt, int ahgt) nothrow @trusted {
71 assert(awdt >= 0 && ahgt >= 0);
72 sx = sy = 0;
73 mwidth = awdt;
74 mheight = ahgt;
75 if (awdt > 0 && ahgt > 0) {
76 try {
77 mimg = new TrueColorImage(awdt, ahgt);
78 } catch (Exception e) {
79 assert(0, e.toString);
81 mimg.imageData.bytes[] = 0;
85 /// will not clone texture!
86 D2DImage clone () const nothrow @trusted {
87 auto res = new D2DImage();
88 res.sx = sx;
89 res.sy = sy;
90 res.mwidth = mwidth;
91 res.mheight = mheight;
92 if (valid) {
93 try {
94 res.mimg = new TrueColorImage(mwidth, mheight);
95 } catch (Exception e) {
96 assert(0, e.toString);
98 res.mimg.imageData.bytes[] = mimg.imageData.bytes[];
100 return res;
103 void resize (int awdt, int ahgt) nothrow @trusted {
104 assert(awdt >= 0 && ahgt >= 0);
105 if (mwidth != awdt || mheight != ahgt) {
106 mtex = null;
107 mwidth = awdt;
108 mheight = ahgt;
109 sx = sy = 0;
110 if (awdt > 0 && ahgt > 0) {
111 try {
112 mimg = new TrueColorImage(awdt, ahgt);
113 } catch (Exception e) {
114 assert(0, e.toString);
116 mimg.imageData.bytes[] = 0;
119 if (mwidth <= 0 || mheight <= 0) mimg = null;
122 void clear () {
123 //if (mtex !is null) mtex.clear;
124 //if (mimg !is null) mimg.clear;
125 mtex = null;
126 mimg = null;
127 mwidth = mheight = 0;
128 sx = sy = 0;
131 void removeOffset () { sx = sy = 0; }
133 @property const pure nothrow @safe @nogc {
134 bool valid () { pragma(inline, true); return (mimg !is null && mwidth > 0 && mheight > 0); }
135 int width () { pragma(inline, true); return mwidth; }
136 int height () { pragma(inline, true); return mheight; }
138 // DO NOT RESIZE!
139 @property TrueColorImage img () pure nothrow @safe @nogc { pragma(inline, true); return mimg; }
141 Color opIndex (usize y, usize x) const /*pure*/ nothrow @safe @nogc { pragma(inline, true); return getPixel(x, y); }
142 void opIndex (Color clr, usize y, usize x) nothrow @safe @nogc { pragma(inline, true); setPixel(x, y, clr); }
143 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); }
145 Color getPixel (int x, int y) const /*pure*/ nothrow @trusted @nogc {
146 pragma(inline, true);
147 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));
150 void setPixel (int x, int y, Color clr) nothrow @trusted @nogc {
151 pragma(inline, true);
152 if (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight) (cast(Color*)mimg.imageData.bytes.ptr)[y*mwidth+x] = clr;
155 void putPixel (int x, int y, Color clr) nothrow @trusted @nogc {
156 pragma(inline, true);
157 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;
160 void toBlackAndWhite () {
161 if (!valid) return;
162 foreach (int y; 0..mheight) {
163 foreach (int x; 0..mwidth) {
164 Color clr = getPixel(x, y);
165 int i = cast(int)(0.2126*clr.r+0.7152*clr.g+0.0722*clr.b);
166 if (i > 255) i = 255; // just in case
167 setPixel(x, y, Color(i&0xff, i&0xff, i&0xff, clr.a));
172 void toBlackAndWhiteChan(string chan) () {
173 static assert(chan == "red" || chan == "r" || chan == "green" || chan == "g" || chan == "blue" || chan == "b", "invalid channel");
174 if (!valid) return;
175 foreach (int y; 0..mheight) {
176 foreach (int x; 0..mwidth) {
177 Color clr = getPixel(x, y);
178 static if (chan == "red" || chan == "r") ubyte i = clr.r;
179 else static if (chan == "green" || chan == "g") ubyte i = clr.g;
180 else static if (chan == "blue" || chan == "b") ubyte i = clr.b;
181 else static assert(0, "wtf?!");
182 setPixel(x, y, Color(i, i, i, clr.a));
187 D2DImage blackAndWhite () {
188 auto res = this.clone();
189 res.toBlackAndWhite;
190 return res;
193 D2DImage blackAndWhiteChan(string chan) () {
194 auto res = this.clone();
195 res.toBlackAndWhiteChan!chan;
196 return res;
199 /// mirror image horizontally
200 void mirror () {
201 if (!valid) return;
202 foreach (int y; 0..height) {
203 foreach (int x; 0..width/2) {
204 int mx = width-x-1;
205 auto c0 = getPixel(x, y);
206 auto c1 = getPixel(mx, y);
207 setPixel(x, y, c1);
208 setPixel(mx, y, c0);
211 sx = width-sx-1;
214 void createTex () {
215 if (mtex is null && valid) mtex = new Texture(mimg, Texture.Option.Nearest);
218 void updateTex () {
219 if (valid) {
220 if (mtex is null) mtex = new Texture(mimg, Texture.Option.Nearest); else mtex.setFromImage(mimg);
224 GLuint asTex () {
225 if (!valid) return 0;
226 if (mtex is null) createTex();
227 return (mtex !is null ? mtex.id : 0);
230 void savePng (string fname) {
231 if (!valid) throw new Exception("can't save empty image");
232 auto png = pngFromImage(mimg);
234 D2DInfoChunk di;
235 di.ver = 0;
236 di.sx = sx;
237 di.sy = sy;
238 di.fixEndian;
239 auto chk = Chunk.create("dtDi", (cast(ubyte*)&di)[0..di.sizeof]);
240 png.chunks ~= *chk;
242 // zdoom chunk
243 if (sx >= short.min && sx <= short.max && sy >= short.min && sy <= short.max) {
244 GrabChunk di;
245 di.sx = cast(short)sx;
246 di.sy = cast(short)sy;
247 di.fixEndian;
248 auto chk = Chunk.create("grAb", (cast(ubyte*)&di)[0..di.sizeof]);
249 png.chunks ~= *chk;
251 auto fo = vfsDiskOpen(fname, "w");
252 fo.rawWriteExact(writePng(png));
255 private:
256 static align(1) struct D2DInfoChunk {
257 align(1):
258 ubyte ver; // version; 0 for now; versions should be compatible
259 int sx, sy;
261 void fixEndian () nothrow @trusted @nogc {
262 version(BigEndian) {
263 import std.bitmanip : swapEndian;
264 sx = swapEndian(sx);
265 sy = swapEndian(sy);
270 static align(1) struct GrabChunk {
271 align(1):
272 short sx, sy;
274 void fixEndian () nothrow @trusted @nogc {
275 version(LittleEndian) {
276 import std.bitmanip : swapEndian;
277 sx = swapEndian(sx);
278 sy = swapEndian(sy);
283 void loadPng (VFile fl) {
284 auto flsize = fl.size-fl.tell;
285 if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big");
286 auto data = new ubyte[](cast(uint)flsize);
287 fl.rawReadExact(data);
288 auto png = readPng(data);
289 auto ximg = imageFromPng(png).getAsTrueColorImage;
290 if (ximg is null) throw new Exception("png: wtf?!");
291 if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small");
292 mwidth = ximg.width;
293 mheight = ximg.height;
294 sx = sy = 0;
295 mimg = ximg;
296 foreach (ref chk; png.chunks) {
297 if (chk.type[] == "dtDi") {
298 // d2d info chunk
299 if (chk.size >= D2DInfoChunk.sizeof) {
300 auto di = *cast(D2DInfoChunk*)chk.payload.ptr;
301 di.fixEndian;
302 sx = di.sx;
303 sy = di.sy;
304 //conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
306 } else if (chk.type[] == "grAb") {
307 // zdoom info chunk
308 if (chk.size >= GrabChunk.sizeof) {
309 auto di = *cast(GrabChunk*)chk.payload.ptr;
310 di.fixEndian;
311 sx = di.sx;
312 sy = di.sy;
313 //conwriteln("found 'dtDi' chunk! sx=", di.sx, "; sy=", di.sy);
319 void loadVga (VFile fl) {
320 //conwriteln(" loading .VGA image");
321 auto w = fl.readNum!ushort();
322 auto h = fl.readNum!ushort();
323 auto isx = fl.readNum!short();
324 auto isy = fl.readNum!short();
325 //conwriteln(" loading .VGA image; w=", w, "; h=", h, "; isx=", isx, "; isy=", isy);
326 if (w < 1 || w > 32760) throw new Exception("invalid vga image mwidth");
327 if (h < 1 || h > 32760) throw new Exception("invalid vga image mheight");
328 auto data = new ubyte[](w*h);
329 fl.rawReadExact(data[]);
330 resize(w, h);
331 assert(mimg !is null);
332 assert(mimg.width == w);
333 assert(mimg.height == h);
334 assert(mwidth == w);
335 assert(mheight == h);
336 //conwriteln(" !!!");
337 sx = isx;
338 sy = isy;
339 foreach (int y; 0..height) {
340 foreach (int x; 0..width) {
341 setPixel(x, y, d2dpal.ptr[data.ptr[y*w+x]]);
346 void load (VFile fl) {
347 auto stp = fl.tell;
348 scope(failure) fl.seek(stp);
349 char[8] sign;
350 fl.rawReadExact(sign[]);
351 fl.seek(stp);
352 if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
353 loadPng(fl);
354 } else {
355 loadVga(fl);