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
;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public __gshared Color
[256] d2dpal
;
36 public void loadD2DPalette () {
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);
49 // color 0 is transparent
54 // ////////////////////////////////////////////////////////////////////////// //
55 public final class D2DImage
{
59 private TrueColorImage mimg
;
62 private this () pure nothrow @safe {}
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
) {
70 auto nn
= name
.setExtension(ext
);
72 conwriteln("loading image '", nn
, "'");
75 } catch (Exception e
) {
76 //conwriteln("ERROR: ", e.msg);
80 auto fl
= VFile(name
);
81 conwriteln("loading image '", name
, "'");
85 this (int awdt
, int ahgt
) nothrow @trusted {
86 assert(awdt
>= 0 && ahgt
>= 0);
90 if (awdt
> 0 && ahgt
> 0) {
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();
106 res
.mheight
= mheight
;
109 res
.mimg
= new TrueColorImage(mwidth
, mheight
);
110 } catch (Exception e
) {
111 assert(0, e
.toString
);
113 res
.mimg
.imageData
.bytes
[] = mimg
.imageData
.bytes
[];
118 void resize (int awdt
, int ahgt
) nothrow @trusted {
119 assert(awdt
>= 0 && ahgt
>= 0);
120 if (mwidth
!= awdt || mheight
!= ahgt
) {
125 if (awdt
> 0 && ahgt
> 0) {
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;
138 //if (mtex !is null) mtex.clear;
139 //if (mimg !is null) mimg.clear;
142 mwidth
= mheight
= 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
; }
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 () {
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");
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();
208 D2DImage
blackAndWhiteChan(string chan
) () {
209 auto res
= this.clone();
210 res
.toBlackAndWhiteChan
!chan
;
214 /// mirror image horizontally
217 foreach (int y
; 0..height
) {
218 foreach (int x
; 0..width
/2) {
220 auto c0
= getPixel(x
, y
);
221 auto c1
= getPixel(mx
, y
);
230 if (mtex
is null && valid
) mtex
= new Texture(mimg
, Texture
.Option
.Nearest
);
235 if (mtex
is null) mtex
= new Texture(mimg
, Texture
.Option
.Nearest
); else mtex
.setFromImage(mimg
);
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
;
262 if (sx
!= 0 || sy
!= 0) {
269 auto chk
= Chunk
.create("dtDi", (cast(ubyte*)&di)[0..di.sizeof
]);
270 //png.chunks ~= *chk;
274 if (sx
>= short.min
&& sx
<= short.max
&& sy
>= short.min
&& sy
<= short.max
) {
276 di.sx
= cast(short)sx
;
277 di.sy
= cast(short)sy
;
279 auto chk
= Chunk
.create("grAb", (cast(ubyte*)&di)[0..di.sizeof
]);
280 //png.chunks ~= *chk;
284 auto fo
= vfsDiskOpen(fname
, "w");
285 fo
.rawWriteExact(writePng(png
));
289 static align(1) struct D2DInfoChunk
{
291 ubyte ver
; // version; 0 for now; versions should be compatible
294 void fixEndian () nothrow @trusted @nogc {
296 import std
.bitmanip
: swapEndian
;
303 static align(1) struct GrabChunk
{
307 void fixEndian () nothrow @trusted @nogc {
308 version(LittleEndian
) {
309 import std
.bitmanip
: swapEndian
;
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");
326 mheight
= ximg
.height
;
329 foreach (ref chk
; png
.chunks
) {
330 if (chk
.type
[] == "dtDi") {
332 if (chk
.size
>= D2DInfoChunk
.sizeof
) {
333 auto di = *cast(D2DInfoChunk
*)chk
.payload
.ptr
;
337 version(png_dump_chunks
) conwriteln("found 'dtDi' chunk! sx=", di.sx
, "; sy=", di.sy
);
339 } else if (chk
.type
[] == "grAb") {
341 if (chk
.size
>= GrabChunk
.sizeof
) {
342 auto di = *cast(GrabChunk
*)chk
.payload
.ptr
;
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
[]);
364 assert(mimg
!is null);
365 assert(mimg
.width
== w
);
366 assert(mimg
.height
== h
);
368 assert(mheight
== h
);
369 //conwriteln(" !!!");
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");
385 mheight
= jpg
.height
;
390 void load (VFile fl
) {
391 scope(failure
) fl
.seek(0);
394 fl
.rawReadExact(sign
[]);
396 if (sign
== "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
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") {
416 // alas, this must be vga