Fixed git clone URL
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / renderer / raster / texture / Texture.java
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko.
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.renderer.raster.texture;
6
7 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
8
9 import java.awt.*;
10 import java.awt.image.BufferedImage;
11 import java.awt.image.DataBufferByte;
12 import java.awt.image.WritableRaster;
13
14 import static java.util.Arrays.fill;
15
16 public class Texture {
17
18     public final TextureBitmap primaryBitmap;
19     public final java.awt.Graphics2D graphics;
20     TextureBitmap[] upSampled;
21     TextureBitmap[] downSampled = new TextureBitmap[8];
22
23     public Texture(final int width, final int height, final int maxUpscale) {
24         upSampled = new TextureBitmap[maxUpscale];
25
26         final BufferedImage bufferedImage = new BufferedImage(width, height,
27                 RenderingContext.bufferedImageType);
28
29         final WritableRaster raster = bufferedImage.getRaster();
30         final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
31         graphics = (Graphics2D) bufferedImage.getGraphics();
32
33         graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
34                 RenderingHints.VALUE_ANTIALIAS_ON);
35
36         graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
37                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
38
39         primaryBitmap = new TextureBitmap(width, height, dbi.getData(), 1);
40     }
41
42     public int detectDownscaleFactorForZoom(final double zoom) {
43         double size = 1;
44         for (int i = 0; i < downSampled.length; i++) {
45             size = size / 2;
46             if (size < zoom)
47                 return i;
48         }
49
50         return downSampled.length - 1;
51     }
52
53     public int detectUpscaleFactorForZoom(final double zoom) {
54         double size = 2;
55         for (int i = 0; i < upSampled.length; i++) {
56             size = size * 2;
57             if (size > zoom)
58                 return i;
59         }
60
61         return upSampled.length - 1;
62     }
63
64     /**
65      * Downscale given bitmap by factor of 2.
66      *
67      * @param originalBitmap Bitmap to downscale.
68      * @return Downscaled bitmap.
69      */
70     public TextureBitmap downscaleBitmap(final TextureBitmap originalBitmap) {
71         int newWidth = originalBitmap.width / 2;
72         int newHeight = originalBitmap.height / 2;
73
74         // Enforce minimum width and height
75         if (newWidth < 1)
76             newWidth = 1;
77         if (newHeight < 1)
78             newHeight = 1;
79
80         final TextureBitmap downScaled = new TextureBitmap(newWidth, newHeight,
81                 originalBitmap.multiplicationFactor / 2d);
82
83         final ColorAccumulator accumulator = new ColorAccumulator();
84
85         for (int y = 0; y < newHeight; y++)
86             for (int x = 0; x < newWidth; x++) {
87                 accumulator.reset();
88                 accumulator.accumulate(originalBitmap, x * 2, y * 2);
89                 accumulator.accumulate(originalBitmap, (x * 2) + 1, y * 2);
90                 accumulator.accumulate(originalBitmap, x * 2, (y * 2) + 1);
91                 accumulator
92                         .accumulate(originalBitmap, (x * 2) + 1, (y * 2) + 1);
93                 accumulator.storeResult(downScaled, x, y);
94             }
95
96         return downScaled;
97     }
98
99     public TextureBitmap getDownscaledBitmap(final int scaleFactor) {
100         if (downSampled[scaleFactor] == null) {
101
102             TextureBitmap largerBitmap;
103             if (scaleFactor == 0)
104                 largerBitmap = primaryBitmap;
105             else
106                 largerBitmap = getDownscaledBitmap(scaleFactor - 1);
107
108             downSampled[scaleFactor] = downscaleBitmap(largerBitmap);
109         }
110
111         return downSampled[scaleFactor];
112     }
113
114     /**
115      * Returns the bitmap that should be used for rendering at the given zoom
116      *
117      * @param scaleFactor The upscale factor
118      * @return The bitmap
119      */
120     public TextureBitmap getUpscaledBitmap(final int scaleFactor) {
121         if (upSampled[scaleFactor] == null) {
122
123             TextureBitmap smallerBitmap;
124             if (scaleFactor == 0)
125                 smallerBitmap = primaryBitmap;
126             else
127                 smallerBitmap = getUpscaledBitmap(scaleFactor - 1);
128
129             upSampled[scaleFactor] = upscaleBitmap(smallerBitmap);
130         }
131
132         return upSampled[scaleFactor];
133     }
134
135     /**
136      * Returns the bitmap that should be used for rendering at the given zoom
137      *
138      * @param zoomLevel The zoom level
139      * @return The bitmap
140      */
141     public TextureBitmap getZoomedBitmap(final double zoomLevel) {
142
143         if (zoomLevel < 1) {
144             final int downscaleFactor = detectDownscaleFactorForZoom(zoomLevel);
145             return getDownscaledBitmap(downscaleFactor);
146         } else if (zoomLevel > 2) {
147             final int upscaleFactor = detectUpscaleFactorForZoom(zoomLevel);
148
149             if (upscaleFactor < 0)
150                 return primaryBitmap;
151
152             return getUpscaledBitmap(upscaleFactor);
153         }
154
155         // System.out.println(zoomLevel);
156         return primaryBitmap;
157     }
158
159     /**
160      * Resets the cache of resampled bitmaps
161      */
162     public void resetResampledBitmapCache() {
163         fill(upSampled, null);
164
165         fill(downSampled, null);
166     }
167
168     /**
169      * Upscales the given bitmap by a factor of 2
170      *
171      * @param originalBitmap The bitmap to upscale
172      * @return The upscaled bitmap
173      */
174     public TextureBitmap upscaleBitmap(final TextureBitmap originalBitmap) {
175         final int newWidth = originalBitmap.width * 2;
176         final int newHeight = originalBitmap.height * 2;
177
178         final TextureBitmap upScaled = new TextureBitmap(newWidth, newHeight,
179                 originalBitmap.multiplicationFactor * 2d);
180
181         final ColorAccumulator accumulator = new ColorAccumulator();
182
183         for (int y = 0; y < originalBitmap.height; y++)
184             for (int x = 0; x < originalBitmap.width; x++) {
185                 accumulator.reset();
186                 accumulator.accumulate(originalBitmap, x, y);
187                 accumulator.storeResult(upScaled, x * 2, y * 2);
188
189                 accumulator.reset();
190                 accumulator.accumulate(originalBitmap, x, y);
191                 accumulator.accumulate(originalBitmap, x + 1, y);
192                 accumulator.storeResult(upScaled, (x * 2) + 1, y * 2);
193
194                 accumulator.reset();
195                 accumulator.accumulate(originalBitmap, x, y);
196                 accumulator.accumulate(originalBitmap, x, y + 1);
197                 accumulator.storeResult(upScaled, x * 2, (y * 2) + 1);
198
199                 accumulator.reset();
200                 accumulator.accumulate(originalBitmap, x, y);
201                 accumulator.accumulate(originalBitmap, x + 1, y);
202                 accumulator.accumulate(originalBitmap, x, y + 1);
203                 accumulator.accumulate(originalBitmap, x + 1, y + 1);
204                 accumulator.storeResult(upScaled, (x * 2) + 1, (y * 2) + 1);
205             }
206
207         return upScaled;
208     }
209
210     /**
211      * A helper class that accumulates color values for a given area of a bitmap
212      */
213     public static class ColorAccumulator {
214         // Accumulated color values
215         public int r, g, b, a;
216
217         // Number of pixels that have been accumulated
218         public int pixelCount = 0;
219
220         /**
221          * Accumulates the color values of the given pixel
222          *
223          * @param bitmap The bitmap
224          * @param x      The x coordinate of the pixel
225          * @param y      The y coordinate of the pixel
226          */
227         public void accumulate(final TextureBitmap bitmap, final int x,
228                                final int y) {
229             int address = bitmap.getAddress(x, y);
230
231             a += bitmap.bytes[address] & 0xff;
232             address++;
233
234             b += bitmap.bytes[address] & 0xff;
235             address++;
236
237             g += bitmap.bytes[address] & 0xff;
238             address++;
239
240             r += bitmap.bytes[address] & 0xff;
241
242             pixelCount++;
243         }
244
245         /**
246          * Resets the accumulator
247          */
248         public void reset() {
249             a = 0;
250             r = 0;
251             g = 0;
252             b = 0;
253             pixelCount = 0;
254         }
255
256         /**
257          * Stores the accumulated color values in the given bitmap
258          *
259          * @param bitmap The bitmap
260          * @param x      The x coordinate of the pixel
261          * @param y      The y coordinate of the pixel
262          */
263         public void storeResult(final TextureBitmap bitmap, final int x,
264                                 final int y) {
265             int address = bitmap.getAddress(x, y);
266
267             bitmap.bytes[address] = (byte) (a / pixelCount);
268             address++;
269
270             bitmap.bytes[address] = (byte) (b / pixelCount);
271             address++;
272
273             bitmap.bytes[address] = (byte) (g / pixelCount);
274             address++;
275
276             bitmap.bytes[address] = (byte) (r / pixelCount);
277         }
278     }
279
280 }