Create TextureAtlas with TextureRegions from bitmaps (#283)
This commit is contained in:
parent
a2852b0761
commit
f67c457006
176
vtm/src/org/oscim/utils/TextureAtlasUtils.java
Normal file
176
vtm/src/org/oscim/utils/TextureAtlasUtils.java
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2017 Longri
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.utils;
|
||||
|
||||
import org.oscim.backend.CanvasAdapter;
|
||||
import org.oscim.backend.canvas.Bitmap;
|
||||
import org.oscim.backend.canvas.Canvas;
|
||||
import org.oscim.renderer.atlas.TextureAtlas;
|
||||
import org.oscim.renderer.atlas.TextureRegion;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Longri on 26.01.2017.
|
||||
*/
|
||||
public class TextureAtlasUtils {
|
||||
|
||||
private final static int MAX_ATLAS_SIZE = 1024;
|
||||
private final static int PAD = 2;
|
||||
|
||||
/**
|
||||
* Create a Map<Object, TextureRegion> from Map<Object, Bitmap>!
|
||||
* <br/>
|
||||
* The List<TextureAtlas> contains the generated TextureAtlas object, for disposing if no longer needed!<br/>
|
||||
* With tha param disposeBitmap, all Bitmaps will released!<br/>
|
||||
* With parameter flipY, the Atlas TextureItem will flipped over Y. (Is needed by iOS)<br/>
|
||||
* @param inputMap Map<Object, Bitmap> input Map with all Bitmaps, from which the regions are to be created
|
||||
* @param outputMap Map<Object, TextureRegion> contains all generated TextureRegions
|
||||
* @param atlasList List<TextureAtlas> contains all created TextureAtlases
|
||||
* @param disposeBitmaps boolean (will recycle all Bitmap's)
|
||||
* @param flipY boolean (set True with iOS)
|
||||
*/
|
||||
public static void createTextureRegions(final Map<Object, Bitmap> inputMap, Map<Object, TextureRegion> outputMap,
|
||||
List<TextureAtlas> atlasList, boolean disposeBitmaps, boolean flipY) {
|
||||
|
||||
// step 1: sort inputMap by Bitmap size
|
||||
List<Map.Entry<Object, Bitmap>> list =
|
||||
new LinkedList<Map.Entry<Object, Bitmap>>(inputMap.entrySet());
|
||||
|
||||
Collections.sort(list, new Comparator<Map.Entry<Object, Bitmap>>() {
|
||||
public int compare(Map.Entry<Object, Bitmap> o1, Map.Entry<Object, Bitmap> o2) {
|
||||
int width1 = o1.getValue().getWidth();
|
||||
int width2 = o2.getValue().getWidth();
|
||||
int height1 = o1.getValue().getHeight();
|
||||
int height2 = o2.getValue().getHeight();
|
||||
return Math.max(width2, height2) - Math.max(width1, height1);
|
||||
}
|
||||
});
|
||||
|
||||
Map<Object, Object> sortedByValues = new LinkedHashMap<Object, Object>();
|
||||
for (Map.Entry<Object, Bitmap> entry : list) {
|
||||
sortedByValues.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
|
||||
//step 2: calculate Atlas count and size
|
||||
int completePixel = PAD * PAD;
|
||||
for (Map.Entry<Object, Object> entry : sortedByValues.entrySet()) {
|
||||
completePixel += (((Bitmap) entry.getValue()).getWidth() + PAD)
|
||||
* (((Bitmap) entry.getValue()).getHeight() + PAD);
|
||||
}
|
||||
completePixel *= 1.2; // add estimated blank pixels
|
||||
int atlasWidth = (int) Math.sqrt(completePixel);
|
||||
int atlasCount = (atlasWidth / MAX_ATLAS_SIZE) + 1;
|
||||
if (atlasCount > 1) atlasWidth = MAX_ATLAS_SIZE;
|
||||
|
||||
|
||||
//step 3: replace value object with object that holds the Bitmap and a Rectangle
|
||||
for (Map.Entry<Object, Object> entry : sortedByValues.entrySet()) {
|
||||
BmpRectangleObject newObject = new BmpRectangleObject();
|
||||
newObject.bitmap = (Bitmap) entry.getValue();
|
||||
newObject.rec = new TextureAtlas.Rect(0, 0,
|
||||
newObject.bitmap.getWidth(), newObject.bitmap.getHeight());
|
||||
entry.setValue(newObject);
|
||||
}
|
||||
|
||||
|
||||
//step 4: calculate Regions(rectangles) and split to atlases
|
||||
List<AtlasElement> atlases = new ArrayList<AtlasElement>();
|
||||
atlases.add(new AtlasElement());
|
||||
int atlasIndex = 0;
|
||||
int maxLineHeight = PAD;
|
||||
for (Map.Entry<Object, Object> entry : sortedByValues.entrySet()) {
|
||||
BmpRectangleObject obj = (BmpRectangleObject) entry.getValue();
|
||||
AtlasElement atlas = atlases.get(atlasIndex);
|
||||
if ((atlas.width + obj.rec.w + PAD) > atlasWidth) {
|
||||
// not enough space, try next line
|
||||
if ((atlas.height + maxLineHeight + obj.rec.h + PAD) > MAX_ATLAS_SIZE) {
|
||||
// not enough space, take new Atlas
|
||||
atlas.width = atlasWidth;
|
||||
atlas.height += PAD + maxLineHeight;
|
||||
atlasIndex++;
|
||||
atlas = new AtlasElement();
|
||||
atlases.add(atlas);
|
||||
|
||||
obj.rec.x = PAD + atlas.width;
|
||||
obj.rec.y = PAD + atlas.height;
|
||||
atlas.width += obj.rec.w + PAD;
|
||||
maxLineHeight = obj.rec.h + PAD;
|
||||
} else {
|
||||
// new line
|
||||
atlas.width = 0;
|
||||
atlas.height += PAD + maxLineHeight;
|
||||
obj.rec.x = PAD + atlas.width;
|
||||
obj.rec.y = PAD + atlas.height;
|
||||
atlas.width += obj.rec.w + PAD;
|
||||
}
|
||||
} else {
|
||||
// new row
|
||||
obj.rec.x = PAD + atlas.width;
|
||||
obj.rec.y = PAD + atlas.height;
|
||||
atlas.width += obj.rec.w + PAD;
|
||||
maxLineHeight = Math.max(maxLineHeight, obj.rec.h + PAD);
|
||||
}
|
||||
atlas.map.put(entry.getKey(), obj);
|
||||
}
|
||||
AtlasElement lastAtlas = atlases.get(atlases.size() - 1);
|
||||
lastAtlas.width = atlasWidth;
|
||||
lastAtlas.height += maxLineHeight;
|
||||
|
||||
|
||||
//step 5: create TextureAtlases and there TextureRegions
|
||||
for (AtlasElement atlas : atlases) {
|
||||
Bitmap atlasBitmap = CanvasAdapter.newBitmap(atlas.width, atlas.height, 0);
|
||||
Canvas canvas = CanvasAdapter.newCanvas();
|
||||
canvas.setBitmap(atlasBitmap);
|
||||
|
||||
// draw regions into texture
|
||||
for (Map.Entry<Object, Object> entry : atlas.map.entrySet()) {
|
||||
BmpRectangleObject obj = (BmpRectangleObject) entry.getValue();
|
||||
if (obj.rec.x + obj.rec.w > atlasBitmap.getWidth() ||
|
||||
obj.rec.y + obj.rec.h > atlasBitmap.getHeight()) {
|
||||
throw new RuntimeException("atlas region outside of textureRegion");
|
||||
}
|
||||
canvas.drawBitmap(obj.bitmap, obj.rec.x, flipY ? atlas.height - obj.rec.y - obj.rec.h : obj.rec.y);
|
||||
}
|
||||
TextureAtlas textureAtlas = new TextureAtlas(atlasBitmap);
|
||||
atlasList.add(textureAtlas);
|
||||
|
||||
//register regions and put there into outputMap
|
||||
for (Map.Entry<Object, Object> entry : atlas.map.entrySet()) {
|
||||
textureAtlas.addTextureRegion(entry.getKey(), ((BmpRectangleObject) entry.getValue()).rec);
|
||||
outputMap.put(entry.getKey(), textureAtlas.getTextureRegion(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
if (disposeBitmaps) {
|
||||
for (Bitmap bmp : inputMap.values()) {
|
||||
bmp.recycle();
|
||||
}
|
||||
inputMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static class BmpRectangleObject {
|
||||
private Bitmap bitmap;
|
||||
private TextureAtlas.Rect rec;
|
||||
}
|
||||
|
||||
private static class AtlasElement {
|
||||
int width = 0, height = 0;
|
||||
Map<Object, Object> map = new LinkedHashMap<Object, Object>();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user