d2dimage: better png writer
[dd2d.git] / tatlas.d
blob7d351f24a7652fdc0340f8df110da2ef121928a6
1 module tatlas is aliced;
2 private:
4 import arsd.color;
6 import d2dimage;
7 import glutils;
9 //version = tatlas_brect;
12 // ////////////////////////////////////////////////////////////////////////// //
13 public final class TexAtlas {
14 public:
15 static struct Rect {
16 int x, y, w, h;
17 alias x0 = x;
18 alias y0 = y;
19 nothrow @safe @nogc:
20 @property bool valid () const pure { pragma(inline, true); return (x >= 0 && y >= 0 && w > 0 && h > 0); }
21 @property int area () const pure { pragma(inline, true); return w*h; }
22 @property int x1 () const pure { pragma(inline, true); return x+w; }
23 @property int y1 () const pure { pragma(inline, true); return y+h; }
24 static @property Rect Invalid () pure { Rect res; return res; }
27 // texture coords in atlas
28 static struct FRect {
29 float x0, y0, x1, y1;
32 private:
33 public TrueColorImage img;
34 Rect[] rects;
35 public Texture tex;
37 enum BadRect = uint.max;
39 public:
40 this (int awdt, int ahgt) {
41 assert(awdt > 0 && ahgt > 0);
42 img = new TrueColorImage(awdt, ahgt);
43 rects ~= Rect(0, 0, awdt, ahgt); // one big rect
46 @property const /*pure*/ /*nothrow*/ @safe /*@nogc*/ {
47 int width () { pragma(inline, true); return img.width; }
48 int height () { pragma(inline, true); return img.height; }
49 bool hasTexture () { pragma(inline, true); return (tex !is null); }
52 void updateTexture () {
53 version(tatlas_brect) foreach (const ref rc; rects) imgRect(rc.x, rc.y, rc.w, rc.h, Color(0, 255, 0, 255));
54 if (tex is null) {
55 tex = new Texture(img, Texture.Option.Clamp, Texture.Option.Nearest);
56 } else {
57 tex.setFromImage(img);
61 FRect texCoords (in Rect rc) {
62 FRect res;
63 res.x0 = cast(float)rc.x/cast(float)(width);
64 res.y0 = cast(float)rc.y/cast(float)(height);
65 res.x1 = cast(float)(rc.x+rc.w)/cast(float)(width);
66 res.y1 = cast(float)(rc.y+rc.h)/cast(float)(height);
67 return res;
70 // node id or BadRect
71 private uint findBestFit (int w, int h) {
72 uint fitW = BadRect, fitH = BadRect, biggest = BadRect;
74 foreach (immutable idx, const ref r; rects) {
75 if (r.w < w || r.h < h) continue; // absolutely can't fit
76 if (r.w == w && r.h == h) return cast(uint)idx; // perfect fit
77 if (r.w == w) {
78 // width fit
79 if (fitW == BadRect || rects.ptr[fitW].h < r.h) fitW = cast(uint)idx;
80 } else if (r.h == h) {
81 // height fit
82 if (fitH == BadRect || rects.ptr[fitH].w < r.w) fitH = cast(uint)idx;
83 } else {
84 // get biggest rect
85 if (biggest == BadRect || rects.ptr[biggest].area > r.area) biggest = cast(uint)idx;
88 // both?
89 if (fitW != BadRect && fitH != BadRect) return (rects.ptr[fitW].area > rects.ptr[fitH].area ? fitW : fitH);
90 if (fitW != BadRect) return fitW;
91 if (fitH != BadRect) return fitH;
92 return biggest;
95 private void imgPutPixel (int x, int y, Color c) {
96 if (x >= 0 && y >= 0 && x < img.width && y < img.height) img.imageData.colors.ptr[y*img.width+x] = c;
99 private void imgRect (int x, int y, int w, int h, Color c) {
100 foreach (int d; 0..w) { imgPutPixel(x+d, y, c); imgPutPixel(x+d, y+h-1, c); }
101 foreach (int d; 1..h-1) { imgPutPixel(x, y+d, c); imgPutPixel(x+w-1, y+d, c); }
104 private void imgRect4 (int x, int y, int w, int h, Color ct, Color cb, Color cl, Color cr) {
105 foreach (int d; 0..w) { imgPutPixel(x+d, y, ct); imgPutPixel(x+d, y+h-1, cb); }
106 foreach (int d; 1..h-1) { imgPutPixel(x, y+d, cl); imgPutPixel(x+w-1, y+d, cr); }
109 // returns invalid rect if there's no room
110 Rect insert (D2DImage di) {
111 assert(di !is null && di.width > 0 && di.height > 0);
112 auto res = insert(di.img);
113 return res;
116 Rect insert (TrueColorImage ximg) {
117 assert(ximg !is null && ximg.width > 0 && ximg.height > 0);
118 auto ri = findBestFit(ximg.width, ximg.height);
119 if (ri == BadRect) return Rect.Invalid;
120 auto rc = rects.ptr[ri];
121 auto res = Rect(rc.x, rc.y, ximg.width, ximg.height);
122 // split this rect
123 if (rc.w == res.w && rc.h == res.h) {
124 // best fit, simply remove this rect
125 foreach (immutable cidx; ri+1..rects.length) rects.ptr[cidx-1] = rects.ptr[cidx];
126 rects.length -= 1;
127 rects.assumeSafeAppend; // for future; we probably won't have alot of best-fitting nodes initially
128 } else {
129 if (rc.w == res.w) {
130 // split vertically
131 rc.y += res.h;
132 rc.h -= res.h;
133 } else if (rc.h == res.h) {
134 // split horizontally
135 rc.x += res.w;
136 rc.w -= res.w;
137 } else {
138 Rect nr = rc;
139 // split in both directions (by longer edge)
140 if (rc.w-res.w > rc.h-res.h) {
141 // cut the right part
142 nr.x += res.w;
143 nr.w -= res.w;
144 // cut the bottom part
145 rc.y += res.h;
146 rc.h -= res.h;
147 rc.w = res.w;
148 } else {
149 // cut the bottom part
150 nr.y += res.h;
151 nr.h -= res.h;
152 // cut the right part
153 rc.x += res.w;
154 rc.w -= res.w;
155 rc.h = res.h;
157 rects ~= nr;
159 rects.ptr[ri] = rc;
161 // copy image data
162 auto sp = ximg.imageData.colors.ptr;
163 auto dp = img.imageData.colors.ptr+res.y*img.width+res.x;
164 foreach (immutable dy; 0..ximg.height) {
165 dp[0..ximg.width] = sp[0..ximg.width];
166 sp += ximg.width;
167 dp += img.width;
169 version(tatlas_brect) imgRect4(res.x, res.y, res.w, res.h, Color(255, 0, 0), Color(255, 255, 0), Color(0, 0, 255), Color(0, 255, 0));
170 return res;