6009bb8025d4409fe4f34b593998261ee2c6031c
[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      * @param scaleFactor The upscale factor
117      * @return The bitmap
118      */
119     public TextureBitmap getUpscaledBitmap(final int scaleFactor) {
120         if (upSampled[scaleFactor] == null) {
121
122             TextureBitmap smallerBitmap;
123             if (scaleFactor == 0)
124                 smallerBitmap = primaryBitmap;
125             else
126                 smallerBitmap = getUpscaledBitmap(scaleFactor - 1);
127
128             upSampled[scaleFactor] = upscaleBitmap(smallerBitmap);
129         }
130
131         return upSampled[scaleFactor];
132     }
133
134     /**
135      * Returns the bitmap that should be used for rendering at the given zoom
136      * @param zoomLevel The zoom level
137      * @return The bitmap
138      */
139     public TextureBitmap getZoomedBitmap(final double zoomLevel) {
140
141         if (zoomLevel < 1) {
142             final int downscaleFactor = detectDownscaleFactorForZoom(zoomLevel);
143             return getDownscaledBitmap(downscaleFactor);
144         } else if (zoomLevel > 2) {
145             final int upscaleFactor = detectUpscaleFactorForZoom(zoomLevel);
146
147             if (upscaleFactor < 0)
148                 return primaryBitmap;
149
150             return getUpscaledBitmap(upscaleFactor);
151         }
152
153         // System.out.println(zoomLevel);
154         return primaryBitmap;
155     }
156
157     /**
158      * Resets the cache of resampled bitmaps
159      */
160     public void resetResampledBitmapCache() {
161         fill(upSampled, null);
162
163         fill(downSampled, null);
164     }
165
166     /**
167      * Upscales the given bitmap by a factor of 2
168      * @param originalBitmap The bitmap to upscale
169      * @return The upscaled bitmap
170      */
171     public TextureBitmap upscaleBitmap(final TextureBitmap originalBitmap) {
172         final int newWidth = originalBitmap.width * 2;
173         final int newHeight = originalBitmap.height * 2;
174
175         final TextureBitmap upScaled = new TextureBitmap(newWidth, newHeight,
176                 originalBitmap.multiplicationFactor * 2d);
177
178         final ColorAccumulator accumulator = new ColorAccumulator();
179
180         for (int y = 0; y < originalBitmap.height; y++)
181             for (int x = 0; x < originalBitmap.width; x++) {
182                 accumulator.reset();
183                 accumulator.accumulate(originalBitmap, x, y);
184                 accumulator.storeResult(upScaled, x * 2, y * 2);
185
186                 accumulator.reset();
187                 accumulator.accumulate(originalBitmap, x, y);
188                 accumulator.accumulate(originalBitmap, x + 1, y);
189                 accumulator.storeResult(upScaled, (x * 2) + 1, y * 2);
190
191                 accumulator.reset();
192                 accumulator.accumulate(originalBitmap, x, y);
193                 accumulator.accumulate(originalBitmap, x, y + 1);
194                 accumulator.storeResult(upScaled, x * 2, (y * 2) + 1);
195
196                 accumulator.reset();
197                 accumulator.accumulate(originalBitmap, x, y);
198                 accumulator.accumulate(originalBitmap, x + 1, y);
199                 accumulator.accumulate(originalBitmap, x, y + 1);
200                 accumulator.accumulate(originalBitmap, x + 1, y + 1);
201                 accumulator.storeResult(upScaled, (x * 2) + 1, (y * 2) + 1);
202             }
203
204         return upScaled;
205     }
206
207     /**
208      * A helper class that accumulates color values for a given area of a bitmap
209      */
210     public static class ColorAccumulator {
211         // Accumulated color values
212         public int r, g, b, a;
213
214         // Number of pixels that have been accumulated
215         public int pixelCount = 0;
216
217         /**
218          * Accumulates the color values of the given pixel
219          * @param bitmap The bitmap
220          * @param x The x coordinate of the pixel
221          * @param y The y coordinate of the pixel
222          */
223         public void accumulate(final TextureBitmap bitmap, final int x,
224                                final int y) {
225             int address = bitmap.getAddress(x, y);
226
227             a += bitmap.bytes[address] & 0xff;
228             address++;
229
230             b += bitmap.bytes[address] & 0xff;
231             address++;
232
233             g += bitmap.bytes[address] & 0xff;
234             address++;
235
236             r += bitmap.bytes[address] & 0xff;
237
238             pixelCount++;
239         }
240
241         /**
242          * Resets the accumulator
243          */
244         public void reset() {
245             a = 0;
246             r = 0;
247             g = 0;
248             b = 0;
249             pixelCount = 0;
250         }
251
252         /**
253          * Stores the accumulated color values in the given bitmap
254          * @param bitmap The bitmap
255          * @param x The x coordinate of the pixel
256          * @param y The y coordinate of the pixel
257          */
258         public void storeResult(final TextureBitmap bitmap, final int x,
259                                 final int y) {
260             int address = bitmap.getAddress(x, y);
261
262             bitmap.bytes[address] = (byte) (a / pixelCount);
263             address++;
264
265             bitmap.bytes[address] = (byte) (b / pixelCount);
266             address++;
267
268             bitmap.bytes[address] = (byte) (g / pixelCount);
269             address++;
270
271             bitmap.bytes[address] = (byte) (r / pixelCount);
272         }
273     }
274
275 }