Created
June 9, 2020 09:03
-
-
Save tatarize/ecf70f05bb814169388397c24cce57fe to your computer and use it in GitHub Desktop.
BitmapTileBuilder.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.photoembroidery.tat.olsennoise; | |
import android.graphics.Canvas; | |
import android.graphics.Paint; | |
import android.graphics.Rect; | |
import android.util.SparseArray; | |
import com.photoembroidery.tat.olsennoise.olsennoise.RenderscriptOlsenNoise; | |
import java.util.ArrayList; | |
import java.util.concurrent.LinkedBlockingQueue; | |
/** | |
* Created by Tat on 10/13/2015. | |
* For OlsenNoiseActivity, | |
* Copyright David Olsen, | |
* All Rights Reserved. | |
*/ | |
public class BitmapTileBuilder { | |
static final int CHUNKOFFSET = 8; | |
static final int CHUNKSIZE = 1 << CHUNKOFFSET; | |
static final int BITMAPSIZE = CHUNKSIZE + RenderscriptOlsenNoise.BLUR_EDGE + RenderscriptOlsenNoise.SCALE_FACTOR; | |
private Rect viewChunks; | |
private double viewPortleft, viewPorttop, viewPortwidth, viewPortheight; | |
private Rect validChunks; | |
private float scale = 1f; | |
private double offsetX, offsetY; | |
private int cycle = 0; | |
FieldPositionBitmapFiller filler; | |
SparseArray<Chunk> chunkInSpace = new SparseArray<>(); | |
LinkedBlockingQueue<Chunk> requests = new LinkedBlockingQueue<>(); | |
private ArrayList<Chunk> reuseChunk = new ArrayList<>(); | |
private ArrayList<Chunk> usedChunk = new ArrayList<>(); | |
private Paint paint = new Paint(); | |
public Listener listener; | |
Thread requestProcessor, validator; | |
private static final Boolean[] validateLock = new Boolean[]{false}; | |
public BitmapTileBuilder(FieldPositionBitmapFiller filler, Rect physicalSize) { | |
this.filler = filler; | |
viewPortleft = physicalSize.left; | |
viewPorttop = physicalSize.top; | |
viewPortwidth = physicalSize.width(); | |
viewPortheight = physicalSize.height(); | |
this.viewChunks = new Rect(); | |
this.validChunks = new Rect(); | |
startRequestConsumer(); | |
startValidator(); | |
postValidation(); | |
} | |
private void postValidation() { | |
synchronized (validateLock) { | |
validateLock[0] = true; | |
validateLock.notify(); | |
} | |
} | |
private void startValidator() { | |
validator = new Thread(new Runnable() { | |
@Override | |
public void run() { | |
while (true) { | |
if (Thread.currentThread().isInterrupted()) return; | |
if (!validateLock[0]) { | |
synchronized (validateLock) { | |
try { | |
validateLock.wait(); | |
} catch (InterruptedException e) { | |
return; | |
} | |
} | |
} | |
validateLock[0] = false; | |
validateChunks(); | |
} | |
} | |
}); | |
validator.start(); | |
} | |
private void startRequestConsumer() { | |
requestProcessor = new Thread(new Runnable() { | |
@Override | |
public void run() { | |
while (true) { | |
Chunk chunk; | |
if (Thread.currentThread().isInterrupted()) return; | |
try { | |
chunk = requests.take(); | |
} catch (InterruptedException e) { | |
return; | |
} | |
if (chunk.requested) { | |
synchronized (chunk) { | |
if (chunk.requested) { | |
if (chunk.getBitmap() != null) { | |
filler.request(chunk.getBitmap(), chunk.x << CHUNKOFFSET, chunk.y << CHUNKOFFSET); | |
chunk.filled = true; | |
chunk.requested = false; | |
} | |
} | |
} | |
if (listener != null) listener.filledChunk(chunk); | |
} | |
if (requests.isEmpty()) { | |
if (listener != null) listener.filledComplete(); | |
} | |
} | |
} | |
}); | |
requestProcessor.start(); | |
} | |
public synchronized void clear() { | |
listener = null; | |
requestProcessor.interrupt(); | |
requestProcessor = null; | |
validator.interrupt(); | |
validator = null; | |
this.filler = null; | |
for (Chunk chunk : reuseChunk) { | |
chunk.clear(); | |
} | |
for (Chunk chunk : usedChunk) { | |
chunk.clear(); | |
} | |
} | |
private Chunk getChunk(int x, int y) { | |
Chunk chunk; | |
int key = Chunk.getChunkKey(x, y); | |
chunk = chunkInSpace.get(key); | |
return chunk; | |
} | |
private static void setChunkRect(double scale, double left, double top, double width, double height, Rect rect) { | |
int scaledIntLeft = (int) Math.floor(left / scale); | |
int scaledIntTop = (int) Math.floor(top / scale); | |
int scaledIntRight = (int) Math.ceil((left + width) / scale); | |
int scaledIntBottom = (int) Math.ceil((top + height) / scale); | |
int leftChunk = scaledIntLeft >> CHUNKOFFSET; | |
int topChunk = scaledIntTop >> CHUNKOFFSET; | |
int rightChunk = (scaledIntRight >> CHUNKOFFSET) + 1; | |
int bottomChunk = (scaledIntBottom >> CHUNKOFFSET) + 1; | |
rect.set(leftChunk, topChunk, rightChunk, bottomChunk); | |
} | |
private synchronized void validateChunks() { | |
setChunkRect(scale, viewPortleft, viewPorttop, viewPortwidth, viewPortheight, viewChunks); | |
offsetX = (viewPortleft / scale) - (double) (viewChunks.left << CHUNKOFFSET); | |
offsetY = (viewPorttop / scale) - (double) (viewChunks.top << CHUNKOFFSET); | |
validChunks.set(viewChunks); | |
validChunks.inset(-1, -1); | |
cycle++; | |
for (int yChunk = validChunks.top; yChunk < validChunks.bottom; yChunk++) { | |
for (int xChunk = validChunks.left; xChunk < validChunks.right; xChunk++) { | |
Chunk chunk = getChunk(xChunk, yChunk); | |
if (chunk == null) continue; | |
chunk.cycleUsed = cycle; | |
} | |
} | |
for (int i = usedChunk.size() - 1; i >= 0; i--) { | |
Chunk chunk = usedChunk.get(i); | |
if (chunk.cycleUsed != cycle) { | |
synchronized (chunk) { | |
chunk.requested = false; | |
chunkInSpace.delete(chunk.getKey()); | |
reuseChunk.add(usedChunk.remove(i)); | |
} | |
} | |
} | |
for (int yChunk = validChunks.top; yChunk < validChunks.bottom; yChunk++) { | |
for (int xChunk = validChunks.left; xChunk < validChunks.right; xChunk++) { | |
Chunk chunk = getChunk(xChunk, yChunk); | |
if (chunk == null) { | |
if (!reuseChunk.isEmpty()) { | |
chunk = reuseChunk.get(0); | |
reuseChunk.remove(0); | |
} | |
if (chunk == null) chunk = new Chunk(); | |
synchronized (chunk) { | |
chunk.set(xChunk, yChunk); | |
usedChunk.add(chunk); | |
chunkInSpace.put(chunk.getKey(), chunk); | |
chunk.requested = true; | |
requests.offer(chunk); | |
} | |
if (listener != null) listener.fillRequested(chunk); | |
} | |
chunk.cycleUsed = cycle; | |
} | |
} | |
} | |
synchronized void draw(Canvas canvas) { | |
canvas.save(); | |
if (scale != 0) canvas.scale(scale, scale); | |
double panY = -offsetY; | |
for (int yChunk = viewChunks.top; yChunk < viewChunks.bottom; yChunk++, panY += CHUNKSIZE) { | |
double panX = -offsetX; | |
for (int xChunk = viewChunks.left; xChunk < viewChunks.right; xChunk++, panX += CHUNKSIZE) { | |
Chunk chunk = getChunk(xChunk, yChunk); | |
if (chunk == null) continue; | |
canvas.save(); | |
canvas.translate((float) panX, (float) panY); | |
chunk.draw(canvas, paint); | |
canvas.restore(); | |
} | |
} | |
canvas.restore(); | |
} | |
boolean isFinished() { | |
return requests.isEmpty(); | |
} | |
void setLocation(double cx, double cy) { | |
this.viewPortleft = cx; | |
this.viewPorttop = cy; | |
postValidation(); | |
} | |
void modifyViewPortDimensions(int displayWidth, int displayHeight) { | |
this.viewPortwidth = displayWidth; | |
this.viewPortheight = displayHeight; | |
postValidation(); | |
} | |
public void setScale(int scale) { | |
this.scale = scale; | |
postValidation(); | |
} | |
void setFiller(FieldPositionBitmapFiller filler) { | |
if (this.filler == filler) return; | |
this.filler = filler; | |
refresh(); | |
} | |
synchronized void refresh() { | |
chunkInSpace.clear(); | |
reuseChunk.addAll(usedChunk); | |
usedChunk.clear(); | |
postValidation(); | |
} | |
void setListener(Listener listener) { | |
this.listener = listener; | |
} | |
public String toString() { | |
return "olsen_noise" + "_x" + viewPortleft + "_y" + viewPorttop; | |
} | |
interface Listener { | |
void filledComplete(); | |
void filledChunk(Chunk chunk); | |
void fillRequested(Chunk chunk); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment