Skip to content

Instantly share code, notes, and snippets.

@tatarize
Created June 9, 2020 09:03
Show Gist options
  • Save tatarize/ecf70f05bb814169388397c24cce57fe to your computer and use it in GitHub Desktop.
Save tatarize/ecf70f05bb814169388397c24cce57fe to your computer and use it in GitHub Desktop.
BitmapTileBuilder.java
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