From c70202d43afa0e578fd804c81393b48ac2ad24b1 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Tue, 5 Apr 2016 02:37:26 +0300 Subject: [PATCH] better image loader; it can load pngs now (with special chunk for offset) --- d2dadefs.d | 2 +- d2dfont.d | 7 +- d2dgfx.d | 226 --------------------------------------- d2dimage.d | 330 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ d2dmap.d | 16 ++- d2dparts.d | 2 +- d2dparts_old.d | 2 +- d2dsprite.d | 3 +- dengapi.d | 2 +- render.d | 5 +- tatlas.d | 5 +- xmain_d2d.d | 7 +- 12 files changed, 351 insertions(+), 256 deletions(-) delete mode 100644 d2dgfx.d create mode 100644 d2dimage.d diff --git a/d2dadefs.d b/d2dadefs.d index 160a557..ccb2245 100644 --- a/d2dadefs.d +++ b/d2dadefs.d @@ -22,7 +22,7 @@ import iv.glbinds; import glutils; import console; import dacs; -import d2dgfx; +import d2dimage; import d2dsprite; diff --git a/d2dfont.d b/d2dfont.d index 808b8bf..db5d1b7 100644 --- a/d2dfont.d +++ b/d2dfont.d @@ -19,14 +19,14 @@ module d2dfont is aliced; private: import arsd.color; -import iv.stream; +import iv.vfs.augs; import iv.glbinds; import glutils; import console; import wadarc; -import d2dgfx; +import d2dimage; import tatlas; import iv.vfs.koi8; @@ -104,9 +104,8 @@ struct D2DFont { fontAtlas = new TexAtlas(asz, asz); foreach (ubyte cc; 0..256) { if (vga[cc] is null) continue; - auto img = (asBW ? vga[cc].getBWImageFromRed : vga[cc].asTCImage); + auto img = (asBW ? vga[cc].blackAndWhiteChan!"red".img : vga[cc].img); auto rc = fontAtlas.insert(img); - vga[cc].releaseImage; if (!rc.valid) { success = false; break; } rects[cc] = rc; } diff --git a/d2dgfx.d b/d2dgfx.d deleted file mode 100644 index a8dee83..0000000 --- a/d2dgfx.d +++ /dev/null @@ -1,226 +0,0 @@ -/* DooM2D: Midnight on the Firing Line - * coded by Ketmar // Invisible Vector - * Understanding is not required. Only obedience. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -module d2dgfx is aliced; -private: - -import arsd.color; -import iv.stream; - -import glutils; -import console; -import wadarc; - - -// ////////////////////////////////////////////////////////////////////////// // -public __gshared Color[256] d2dpal; - - -public void loadPalette () { - auto fl = openFile("playpal.pal"); - foreach (immutable idx; 0..256) { - ubyte r = cast(ubyte)(fl.readNum!ubyte()*4); - ubyte g = cast(ubyte)(fl.readNum!ubyte()*4); - ubyte b = cast(ubyte)(fl.readNum!ubyte()*4); - d2dpal[idx].r = r; - d2dpal[idx].g = g; - d2dpal[idx].b = b; - d2dpal[idx].a = 255; - } - // color 0 is transparent - d2dpal[0].asUint = 0; -} - - -// ////////////////////////////////////////////////////////////////////////// // -public final class D2DImage { -public: - int sx, sy; - int width, height; - ubyte[] data; - TrueColorImage img; - Texture tex; - - this (string name) { - try { - auto fl = openFile(name); - load(fl, false); - return; - } catch (Exception) {} - import std.algorithm : endsWith; - if (name.endsWith("_mirrored.vga")) { - auto fl = openFile(name[0..$-13]~".vga"); - load(fl, true); - return; - } - auto fl = openFile(name); // throw error message - } - - this (int awdt, int ahgt) { - assert(awdt > 0 && ahgt > 0); - sx = sy = 0; - width = awdt; - height = ahgt; - data.length = width*height; - data[] = 0; - } - - @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (data !is null && width > 0 && height > 0); } - - Color opIndex (usize y, usize x) { pragma(inline, true); return (x < width && y < height ? d2dpal.ptr[data.ptr[y*width+x]] : Color(0, 0, 0, 0)); } - - void clear () { - if (tex !is null) tex.clear; - //if (img !is null) img.clear; - tex = null; - img = null; - data = null; - width = height = 0; - sx = sy = 0; - } - - @property TrueColorImage getBWImage () { - auto resimg = new TrueColorImage(width, height); - auto cols = resimg.imageData.colors.ptr; - auto ins = new ubyte[](width*height); - ubyte imax = 0; - foreach (int y; 0..height) { - foreach (int x; 0..width) { - ubyte c = data.ptr[y*width+x]; - if (c != 0) { - Color clr = d2dpal[c]; - int i = cast(int)(0.2126*clr.r+0.7152*clr.g+0.0722*clr.b); - if (i > 255) i = 255; // just in case - ins[y*width+x] = i&0xff; - if (imax < i) imax = i&0xff; - } - } - } - //imax = (255-imax)/4; - imax = 0; - foreach (int y; 0..height) { - foreach (int x; 0..width) { - ubyte c = data.ptr[y*width+x]; - if (c == 0) { - *cols = Color(0, 0, 0, 0); // transparent - } else { - int i = ins[y*width+x]+imax; - *cols = Color(i&0xff, i&0xff, i&0xff, 255); - } - ++cols; - } - } - return resimg; - } - - @property TrueColorImage getBWImageFromRed () { - auto resimg = new TrueColorImage(width, height); - auto cols = resimg.imageData.colors.ptr; - auto ins = new ubyte[](width*height); - ubyte imax = 0; - foreach (int y; 0..height) { - foreach (int x; 0..width) { - ubyte c = data.ptr[y*width+x]; - if (c != 0) { - Color clr = d2dpal[c]; - int i = cast(int)(clr.r); - if (i > 255) i = 255; // just in case - ins[y*width+x] = i&0xff; - if (imax < i) imax = i&0xff; - } - } - } - //imax = (255-imax)/4; - imax = 0; - foreach (int y; 0..height) { - foreach (int x; 0..width) { - ubyte c = data.ptr[y*width+x]; - if (c == 0) { - *cols = Color(0, 0, 0, 0); // transparent - } else { - int i = ins[y*width+x]+imax; - *cols = Color(i&0xff, i&0xff, i&0xff, 255); - } - ++cols; - } - } - return resimg; - } - - @property TrueColorImage asTCImage () { - if (img is null && valid) { - img = new TrueColorImage(width, height); - auto cols = img.imageData.colors.ptr; - foreach (int y; 0..height) { - foreach (int x; 0..width) { - ubyte c = data.ptr[y*width+x]; - if (c == 0) { - *cols = Color(0, 0, 0, 0); // transparent - } else { - *cols = d2dpal[c]; - } - ++cols; - } - } - } - return img; - } - - void releaseImage () { img = null; } - - void createGLTex () { - if (tex is null && valid) tex = new Texture(asTCImage, Texture.Option.Nearest); - } - - @property Texture asGLTex () { - if (tex is null && valid) tex = new Texture(asTCImage, Texture.Option.Nearest); - return tex; - } - - // for bottom-up view - void drawAtXY (int x, int y) { - asGLTex(); - if (tex !is null) { - y += sy-height; - glutils.drawAtXY(tex, x-sx, y); - } - } - -private: - void load(ST) (auto ref ST fi, bool mirrored) if (isReadableStream!ST) { - width = fi.readNum!ushort(); - height = fi.readNum!ushort(); - if (width < 1) assert(0); - if (height < 1) assert(0); - sx = fi.readNum!short(); - sy = fi.readNum!short(); - data = new ubyte[width*height]; - fi.rawReadExact(data[]); - if (mirrored) mirrorVga(); - } - - void mirrorVga () { - auto nd = new ubyte[](data.length); - foreach (int y; 0..height) { - int npos = y*width+width-1; - foreach (int x; 0..width) { - nd[npos--] = data[y*width+x]; - } - } - data = nd; - } -} diff --git a/d2dimage.d b/d2dimage.d new file mode 100644 index 0000000..269277e --- /dev/null +++ b/d2dimage.d @@ -0,0 +1,330 @@ +/* DooM2D: Midnight on the Firing Line + * coded by Ketmar // Invisible Vector + * Understanding is not required. Only obedience. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +module d2dimage is aliced; +private: + +import arsd.color; +import arsd.png; +import iv.vfs; +import iv.glbinds; + +import glutils; +import console; +import wadarc; + + +// ////////////////////////////////////////////////////////////////////////// // +public __gshared Color[256] d2dpal; + + +public void loadD2DPalette () { + ubyte[768] vgapal; + { + auto fl = openFile("playpal.pal"); + fl.rawReadExact(vgapal[]); + foreach (ref ubyte b; vgapal) if (b > 63) b = 63; // just in case + } + foreach (immutable idx; 0..256) { + d2dpal[idx].r = cast(ubyte)(vgapal[idx*3+0]*255/63); + d2dpal[idx].g = cast(ubyte)(vgapal[idx*3+1]*255/63); + d2dpal[idx].b = cast(ubyte)(vgapal[idx*3+2]*255/63); + d2dpal[idx].a = 255; + } + // color 0 is transparent + d2dpal[0].asUint = 0; +} + + +// ////////////////////////////////////////////////////////////////////////// // +public final class D2DImage { +private: + __gshared clr00 = Color(0, 0, 0, 0); + +public: + int sx, sy; + int mwidth, mheight; + private TrueColorImage mimg; + private Texture mtex; + + private this () pure nothrow @safe {} + + this (string name) { + import std.path : extension; + auto fl = openFile(name); + conwriteln("loading image '", name, "'"); + load(fl); + } + + this (int awdt, int ahgt) nothrow @trusted { + assert(awdt >= 0 && ahgt >= 0); + sx = sy = 0; + mwidth = awdt; + mheight = ahgt; + if (awdt > 0 && ahgt > 0) { + try { + mimg = new TrueColorImage(awdt, ahgt); + } catch (Exception e) { + assert(0, e.toString); + } + mimg.imageData.bytes[] = 0; + } + } + + /// will not clone texture! + D2DImage clone () const nothrow @trusted { + auto res = new D2DImage(); + res.sx = sx; + res.sy = sy; + res.mwidth = mwidth; + res.mheight = mheight; + if (valid) { + try { + res.mimg = new TrueColorImage(mwidth, mheight); + } catch (Exception e) { + assert(0, e.toString); + } + res.mimg.imageData.bytes[] = mimg.imageData.bytes[]; + } + return res; + } + + void resize (int awdt, int ahgt) nothrow @trusted { + assert(awdt >= 0 && ahgt >= 0); + if (mwidth != awdt || mheight != ahgt) { + mtex = null; + mwidth = awdt; + mheight = ahgt; + sx = sy = 0; + if (awdt > 0 && ahgt > 0) { + try { + mimg = new TrueColorImage(awdt, ahgt); + } catch (Exception e) { + assert(0, e.toString); + } + mimg.imageData.bytes[] = 0; + } + } + if (mwidth <= 0 || mheight <= 0) mimg = null; + } + + void clear () { + //if (mtex !is null) mtex.clear; + //if (mimg !is null) mimg.clear; + mtex = null; + mimg = null; + mwidth = mheight = 0; + sx = sy = 0; + } + + void removeOffset () { sx = sy = 0; } + + @property const pure nothrow @safe @nogc { + bool valid () { pragma(inline, true); return (mimg !is null && mwidth > 0 && mheight > 0); } + int width () { pragma(inline, true); return mwidth; } + int height () { pragma(inline, true); return mheight; } + } + // DO NOT RESIZE! + @property TrueColorImage img () pure nothrow @safe @nogc { pragma(inline, true); return mimg; } + + Color opIndex (usize y, usize x) const /*pure*/ nothrow @safe @nogc { pragma(inline, true); return getPixel(x, y); } + void opIndex (Color clr, usize y, usize x) nothrow @safe @nogc { pragma(inline, true); setPixel(x, y, clr); } + 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); } + + Color getPixel (int x, int y) const /*pure*/ nothrow @trusted @nogc { + pragma(inline, true); + return (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight ? (cast(const(Color*))mimg.imageData.bytes.ptr)[y*mwidth+x] : clr00); + } + + void setPixel (int x, int y, Color clr) nothrow @trusted @nogc { + pragma(inline, true); + if (mimg !is null && x >= 0 && y >= 0 && x < mwidth && y < mheight) (cast(Color*)mimg.imageData.bytes.ptr)[y*mwidth+x] = clr; + } + + void putPixel (int x, int y, Color clr) nothrow @trusted @nogc { + pragma(inline, true); + 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; + } + + void toBlackAndWhite () { + if (!valid) return; + foreach (int y; 0..mheight) { + foreach (int x; 0..mwidth) { + Color clr = getPixel(x, y); + int i = cast(int)(0.2126*clr.r+0.7152*clr.g+0.0722*clr.b); + if (i > 255) i = 255; // just in case + setPixel(x, y, Color(i&0xff, i&0xff, i&0xff, clr.a)); + } + } + } + + void toBlackAndWhiteChan(string chan) () { + static assert(chan == "red" || chan == "r" || chan == "green" || chan == "g" || chan == "blue" || chan == "b", "invalid channel"); + if (!valid) return; + foreach (int y; 0..mheight) { + foreach (int x; 0..mwidth) { + Color clr = getPixel(x, y); + static if (chan == "red" || chan == "r") ubyte i = clr.r; + else static if (chan == "green" || chan == "g") ubyte i = clr.g; + else static if (chan == "blue" || chan == "b") ubyte i = clr.b; + else static assert(0, "wtf?!"); + setPixel(x, y, Color(i, i, i, clr.a)); + } + } + } + + D2DImage blackAndWhite () { + auto res = this.clone(); + res.toBlackAndWhite; + return res; + } + + D2DImage blackAndWhiteChan(string chan) () { + auto res = this.clone(); + res.toBlackAndWhiteChan!chan; + return res; + } + + /// mirror image horizontally + void mirror () { + if (!valid) return; + foreach (int y; 0..height) { + foreach (int x; 0..width/2) { + int mx = width-x-1; + auto c0 = getPixel(x, y); + auto c1 = getPixel(mx, y); + setPixel(x, y, c1); + setPixel(mx, y, c0); + } + } + sx = width-sx-1; + } + + void createTex () { + if (mtex is null && valid) mtex = new Texture(mimg, Texture.Option.Nearest); + } + + void updateTex () { + if (valid) { + if (mtex is null) mtex = new Texture(mimg, Texture.Option.Nearest); else mtex.setFromImage(mimg); + } + } + + GLuint asTex () { + if (!valid) return 0; + if (mtex is null) createTex(); + return (mtex !is null ? mtex.id : 0); + } + + void savePng (string fname) { + if (!valid) throw new Exception("can't save empty image"); + auto png = pngFromImage(mimg); + { + D2DInfoChunk di; + di.ver = 0; + di.sx = sx; + di.sy = sy; + auto chk = Chunk.create("d2DI", (cast(ubyte*)&di)[0..di.sizeof]); + png.chunks ~= *chk; + } + auto fo = vfsDiskOpen(fname, "w"); + fo.rawWriteExact(writePng(png)); + } + +private: + static align(1) struct D2DInfoChunk { + align(1): + ubyte ver; // version; 0 for now; versions should be compatible + int sx, sy; + + void fixEndian () nothrow @trusted @nogc { + version(BigEndian) { + import std.bitmanip : swapEndian; + size = swapEndian(size); + pksize = swapEndian(pksize); + disk = swapEndian(disk); + } + } + } + + void loadPng (VFile fl) { + auto flsize = fl.size-fl.tell; + if (flsize < 8 || flsize > 1024*1024*32) throw new Exception("png image too big"); + auto data = new ubyte[](cast(uint)flsize); + fl.rawReadExact(data); + auto png = readPng(data); + auto ximg = imageFromPng(png).getAsTrueColorImage; + if (ximg is null) throw new Exception("png: wtf?!"); + if (ximg.width < 1 || ximg.height < 1) throw new Exception("png image too small"); + mwidth = ximg.width; + mheight = ximg.height; + sx = sy = 0; + mimg = ximg; + foreach (ref chk; png.chunks) { + if (chk.type[] == "d2DI") { + // d2d info chunk + if (chk.size >= D2DInfoChunk.sizeof) { + auto di = *cast(D2DInfoChunk*)chk.payload.ptr; + di.fixEndian; + sx = di.sx; + sy = di.sy; + //conwriteln("found 'd2DI' chunk! sx=", di.sx, "; sy=", di.sy); + } + } + } + } + + void loadVga (VFile fl) { + //conwriteln(" loading .VGA image"); + auto w = fl.readNum!ushort(); + auto h = fl.readNum!ushort(); + auto isx = fl.readNum!short(); + auto isy = fl.readNum!short(); + //conwriteln(" loading .VGA image; w=", w, "; h=", h, "; isx=", isx, "; isy=", isy); + if (w < 1 || w > 32760) throw new Exception("invalid vga image mwidth"); + if (h < 1 || h > 32760) throw new Exception("invalid vga image mheight"); + auto data = new ubyte[](w*h); + fl.rawReadExact(data[]); + resize(w, h); + assert(mimg !is null); + assert(mimg.width == w); + assert(mimg.height == h); + assert(mwidth == w); + assert(mheight == h); + //conwriteln(" !!!"); + sx = isx; + sy = isy; + foreach (int y; 0..height) { + foreach (int x; 0..width) { + setPixel(x, y, d2dpal.ptr[data.ptr[y*w+x]]); + } + } + } + + void load (VFile fl) { + auto stp = fl.tell; + scope(failure) fl.seek(stp); + char[8] sign; + fl.rawReadExact(sign[]); + fl.seek(stp); + if (sign == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { + loadPng(fl); + } else { + loadVga(fl); + } + } +} diff --git a/d2dmap.d b/d2dmap.d index 724cc8b..0fb5b1a 100644 --- a/d2dmap.d +++ b/d2dmap.d @@ -19,16 +19,13 @@ module d2dmap is aliced; private: import arsd.color; -import iv.stream; +import iv.vfs.augs; import glutils; import console; import wadarc; -import d2dgfx; -//import d2dtpl; - -//import iv.encoding; +import d2dimage; import iv.vfs.koi8; @@ -83,10 +80,9 @@ void putTile (TrueColorImage img, int x, int y, D2DImage wt) { if (wt is null) return; x -= wt.sx; y -= wt.sy; - auto s = wt.data.ptr; foreach (int dy; 0..wt.height) { foreach (int dx; 0..wt.width) { - img.putPixel(x+dx, y, d2dpal[*s++]); + img.putPixel(x+dx, y, wt.getPixel(dx, dy)); } ++y; } @@ -209,7 +205,7 @@ public: // build OpenGL "megatextures" with the whole level on it void oglBuildMega (uint buildMask=0xffff_ffffu) { - if (skytexgl is null) skytexgl = new Texture(skytex.asTCImage, Texture.Option.Linear, Texture.Option.Repeat); + if (skytexgl is null) skytexgl = new Texture(skytex.img, Texture.Option.Linear, Texture.Option.Repeat); //auto img = new TrueColorImage(width*TileSize, height*TileSize); foreach (immutable type; Front..LightMask+1) { if (teximgs[type] is null) { @@ -504,9 +500,9 @@ private: if (tiles[Back][y*MapSize+x] >= textures.length) tiles[Back][y*MapSize+x] = 0; } } - skytex = new D2DImage(skyname.idup); //"sprites/rsky1.vga"); + skytex = new D2DImage(skyname.idup); //skytex = new D2DImage("sprites/rsky3.vga"); - skytex.sx = skytex.sy = 0; + skytex.removeOffset(); debug { conwriteln(width, "x", height); } } } diff --git a/d2dparts.d b/d2dparts.d index c3bf4c2..e418f62 100644 --- a/d2dparts.d +++ b/d2dparts.d @@ -28,7 +28,7 @@ import console; import wadarc; import d2dmap; -import d2dgfx; +import d2dimage; import dengapi : map, unsyncrandu31; diff --git a/d2dparts_old.d b/d2dparts_old.d index 8688252..75209cb 100644 --- a/d2dparts_old.d +++ b/d2dparts_old.d @@ -34,7 +34,7 @@ import console; import wadarc; import d2dmap; -import d2dgfx; +import d2dimage; import dengapi : map, unsyncrandu31; diff --git a/d2dsprite.d b/d2dsprite.d index c42fd8a..0d51339 100644 --- a/d2dsprite.d +++ b/d2dsprite.d @@ -22,7 +22,7 @@ import iv.glbinds; import glutils; import console; import dacs; -import d2dgfx; +import d2dimage; import tatlas; //version = tatlas_dump; @@ -75,7 +75,6 @@ final: assert(arc.valid); atlases ~= aa; } - vga.releaseImage; atlas = aa; arect = arc; frect = aa.texCoords(arc); diff --git a/dengapi.d b/dengapi.d index b7d8cd7..52ea4be 100644 --- a/dengapi.d +++ b/dengapi.d @@ -29,7 +29,7 @@ import wadarc; import d2dmap; import d2dadefs; -import d2dgfx; +import d2dimage; import d2dsprite; import dacs; diff --git a/render.d b/render.d index 2a7b872..53609ba 100644 --- a/render.d +++ b/render.d @@ -31,12 +31,11 @@ import glutils; import console; import wadarc; -import iv.stream; +import iv.vfs.augs; import d2dmap; import d2dadefs; -//import d2dactors; -import d2dgfx; +import d2dimage; import d2dfont; import dacs; diff --git a/tatlas.d b/tatlas.d index a9bf462..7d351f2 100644 --- a/tatlas.d +++ b/tatlas.d @@ -3,7 +3,7 @@ private: import arsd.color; -import d2dgfx; +import d2dimage; import glutils; //version = tatlas_brect; @@ -109,8 +109,7 @@ public: // returns invalid rect if there's no room Rect insert (D2DImage di) { assert(di !is null && di.width > 0 && di.height > 0); - auto res = insert(di.asTCImage); - di.releaseImage; + auto res = insert(di.img); return res; } diff --git a/xmain_d2d.d b/xmain_d2d.d index f2befd5..8baf311 100644 --- a/xmain_d2d.d +++ b/xmain_d2d.d @@ -29,12 +29,11 @@ import glutils; import console; import wadarc; -import iv.stream; +import iv.vfs.augs; import d2dmap; import d2dadefs; -//import d2dactors; -import d2dgfx; +import d2dimage; import d2dfont; import dacs; @@ -117,7 +116,7 @@ void main (string[] args) { try { registerAPI(); - loadPalette(); + loadD2DPalette(); setOpenGLContextVersion(3, 2); // up to GLSL 150 //openGLContextCompatible = false; -- 2.11.4.GIT