-
-
Save BurntPizza/f0d2d31f843c47ea55f4 to your computer and use it in GitHub Desktop.
This file contains 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.burntpizza.swift2d; | |
import java.awt.*; | |
import java.awt.event.MouseEvent; | |
import java.awt.event.MouseMotionListener; | |
import java.awt.image.BufferedImage; | |
import java.awt.image.DataBufferInt; | |
import java.util.Arrays; | |
import javax.swing.JComponent; | |
import javax.swing.JFrame; | |
import com.burntpizza.swift2d.utils.*; | |
/** | |
* * insert description here * | |
* | |
* @author BurntPizza | |
* | |
*/ | |
public class BufferedContext { | |
@SuppressWarnings("serial") | |
private static class TestPanel extends JComponent implements MouseMotionListener { | |
BufferedContext bc; | |
LagMeter m = new LagMeter("Context + paintImmediately()", new Point(1920 / 2 - 200, 1080 / 6 * 4)); | |
int mx, my, lx, ly; | |
{ | |
setSize(1920 * 3 / 4, 1080 * 3 / 4); | |
setPreferredSize(getSize()); | |
addMouseMotionListener(this); | |
bc = new BufferedContext(getWidth(), getHeight()); | |
bc.setBackground(0, 0, 0); | |
bc.clear(); | |
} | |
@Override | |
public void mouseDragged(MouseEvent e) { | |
mx = e.getX(); | |
my = e.getY(); | |
if (lx == 0 && ly == 0) { | |
lx = mx; | |
ly = my; | |
} | |
m.tick(); | |
//bc.clear(); | |
bc.setColor(128, 128, 128); | |
//bc.drawThickLine(mx, my, lx, ly, 200); | |
bc.fillCircle(mx, my, 100); | |
//bc.fillEllipse(mx - 100, my - 150, 200, 300); | |
m.tick(); | |
//g.dispose(); | |
lx = mx; | |
ly = my; | |
//bc.setColor(255, 0, 0); | |
//bc.drawRect(bc.abx, bc.aby, bc.abw, bc.abh); | |
paintImmediately(/*0, 0, getWidth(), getHeight()*/bc.getActiveBounds()); | |
bc.resetActiveBounds(); | |
} | |
@Override | |
public void mouseMoved(MouseEvent e) { | |
} | |
@Override | |
public void paint(Graphics g) { | |
g.drawImage(bc.getImage(), 0, 0, null); | |
} | |
} | |
public static void main(String[] a) { | |
JFrame j = new JFrame(); | |
j.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
TestPanel p = new TestPanel(); | |
j.add(p); | |
j.pack(); | |
j.setVisible(true); | |
j.setLocationRelativeTo(null); | |
} | |
protected int abx, aby, abw, abh; // active bounds | |
/** | |
* Background color | |
*/ | |
protected int bgr, bgg, bgb, bga, bgc; | |
protected BlendMode blendmode = new Overwrite(); | |
protected int[] buffer; // using INT_ARGB | |
/** | |
* Current painting color | |
*/ | |
// note: they are premultiplied and half-clamped, except alpha | |
protected int cr, cg, cb, ca, cc; | |
protected int crp, cgp, cbp; | |
protected BufferedImage image; | |
protected int sclipx, sclipy, sclipw, scliph; | |
protected boolean stencil, opStencil; | |
protected boolean[] stencilBuffer, opStencilBuffer; | |
protected int w, h; | |
/** | |
* Creates a new context wrapping image | |
*/ | |
public BufferedContext(BufferedImage image) { | |
if (image.getType() != BufferedImage.TYPE_INT_ARGB) | |
throw new IllegalArgumentException("Images to be wrapped must be of type INT_ARGB"); | |
this.image = image; | |
w = image.getWidth(); | |
h = image.getHeight(); | |
buffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); | |
stencilBuffer = new boolean[buffer.length]; | |
opStencilBuffer = new boolean[buffer.length]; | |
abx = w; | |
aby = h; | |
} | |
/** | |
* Creates a blank context of width and height | |
*/ | |
public BufferedContext(int width, int height) { | |
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); | |
w = width; | |
h = height; | |
buffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); | |
stencilBuffer = new boolean[buffer.length]; | |
opStencilBuffer = new boolean[buffer.length]; | |
abx = w; | |
aby = h; | |
} | |
public void beginStencil() { | |
// no point in stencil if we don't need blending, so ignore | |
stencil = !(blendmode instanceof Overwrite); | |
sclipx = w; | |
sclipy = h; | |
sclipw = scliph = 0; | |
} | |
/** | |
* Clears the entire image to it's background color. | |
*/ | |
public void clear() { | |
Arrays.fill(buffer, bgc); | |
abx = 0; | |
aby = 0; | |
abw = w; | |
abh = h; | |
} | |
/** | |
* Clears the specified region to the current background color. | |
* Clips automatically. | |
*/ | |
public void clearRect(int x, int y, int w, int h) { | |
// clip to edges | |
final int nx = MathUtils.clamp(x, this.w, 0); | |
final int len = MathUtils.clamp(w, this.w - (nx - x), 0); | |
final int mh = MathUtils.clamp(y + h, this.h, 0); | |
final int bg = ColorUtils.pack(bgr, bgg, bgb, bga); | |
for (; y < mh; y++) { | |
final int np = this.w * y + nx; | |
Arrays.fill(buffer, np, np + len, bg); // might replace this with tmpBuffer + arraycopy | |
} | |
} | |
/** | |
* Copies the specified region of source to the same region of this image. | |
* Clips automatically. | |
* Requires that source be of type INT_ARGB. | |
*/ | |
// needs testing to verify the clipping is correct | |
public void copyRect(BufferedImage source, int x, int y, int w, int h) { | |
if (source.getType() != BufferedImage.TYPE_INT_ARGB) | |
throw new IllegalArgumentException("Images to be copied must be of type INT_ARGB"); | |
final int[] srcBuffer = ((DataBufferInt) source.getRaster().getDataBuffer()).getData(); | |
// clip to edges | |
final int nx = MathUtils.clamp(x, Math.min(this.w, source.getWidth()), 0); | |
final int len = MathUtils.clamp(w, Math.min(this.w - (nx - x), source.getWidth() - (nx - x)), 0); | |
final int mh = MathUtils.clamp(y + h, Math.min(this.h, source.getHeight()), 0); | |
for (; y < mh; y++) { | |
System.arraycopy(srcBuffer, source.getWidth() * y + nx, buffer, this.w * y + nx, len); | |
} | |
} | |
public void drawCircle(int x, int y, int radius) { | |
//TODO: clipping | |
int xOff = radius, yOff = 0; | |
int radiusError = 1 - xOff; | |
while (xOff >= yOff) { | |
drawPixel(-yOff + x, -xOff + y); // north left top | |
drawPixel(yOff + x, -xOff + y); // north right top | |
drawPixel(-xOff + x, -yOff + y); // north left bottom | |
drawPixel(xOff + x, -yOff + y); // north right bottom | |
drawPixel(-xOff + x, yOff + y); // south left top | |
drawPixel(xOff + x, yOff + y); // south right top | |
drawPixel(-yOff + x, xOff + y); // south left bottom | |
drawPixel(yOff + x, xOff + y); // south right bottom | |
yOff++; | |
if (radiusError < 0) { | |
radiusError += 2 * yOff + 1; | |
} else { | |
xOff--; | |
radiusError += 2 * (yOff - xOff + 1); | |
} | |
} | |
} | |
public void drawLine(int x1, int y1, int x2, int y2) { | |
int dx = Math.abs(x2 - x1); | |
int dy = Math.abs(y2 - y1); | |
int sx = -1; | |
int sy = -1; | |
if (x1 < x2) | |
sx = 1; | |
if (y1 < y2) | |
sy = 1; | |
int err = dx - dy; | |
do { | |
drawPixel(x1, y1); | |
int e2 = 2 * err; | |
if (e2 > -dy) { | |
err = err - dy; | |
x1 = x1 + sx; | |
} | |
if (e2 < dx) { | |
err = err + dx; | |
y1 = y1 + sy; | |
} | |
} while (!(x1 == x2 && y1 == y2)); | |
} | |
public void drawPixel(int x, int y) { | |
if (x >= 0 && x < w && y >= 0 && y < h) | |
drawPixel(x + y * w); | |
} | |
public void drawRect(int x, int y, int width, int height) { | |
//* do clipping * | |
int ox = x, oy = y; | |
if (x < 0) { | |
if (x <= -width) | |
return; | |
width += x; | |
x = 0; | |
} else if (x + width >= w) { | |
if (x >= w) | |
return; | |
width = Math.min(width + x, w); | |
width -= x; | |
} | |
if (y < 0) { | |
if (y <= -height) | |
return; | |
height += y; | |
y = 0; | |
} else if (y + height >= h) { | |
if (y >= h) | |
return; | |
height = Math.min(height + y, h); | |
height -= y; | |
} | |
expandActiveBounds(x, y, width, height); | |
final int wm1 = width - 1; | |
int start = x + y * w; | |
int end = start + width; | |
// top | |
if (oy > 0) | |
for (int i = start; i < end; i++) { | |
drawPixel(i); | |
} | |
end = y + height - 1; | |
// sides | |
if (ox > 0) | |
for (int yi = y + 1; yi < end; yi++) { | |
start += w; | |
drawPixel(start); | |
} | |
start = x + y * w + wm1; | |
if (ox + width < w) | |
for (int yi = y + 1; yi < end; yi++) { | |
start += w; | |
drawPixel(start); | |
} | |
start = x + (y + height - 1) * w; | |
end = start + width; | |
// bottom | |
if (oy + height < h) | |
for (int i = start; i < end; i++) { | |
drawPixel(i); | |
} | |
/* | |
drawLine(x, y, x + w, y); // top side | |
drawLine(x, y + h, x + w, y + h); // bottom side | |
drawLine(x, y + 1, x, y + h - 1); // left side | |
drawLine(x + w, y + 1, x + w, y + h - 1); // right side | |
*/ | |
} | |
public void drawThickLine(int x1, int y1, int x2, int y2, int radius) { | |
// plot a circle at each end, connect with lines | DOESN'T WORK (perfectly) :( | |
// instead plot line, filling a circle at each point (crappy I know) | |
int dx = Math.abs(x2 - x1); | |
int dy = Math.abs(y2 - y1); | |
int sx = -1; | |
int sy = -1; | |
if (x1 < x2) | |
sx = 1; | |
if (y1 < y2) | |
sy = 1; | |
int err = dx - dy; | |
int clipx = Math.min(x1, x2) - radius; | |
int clipy = Math.min(y1, y2) - radius; | |
int clipw = dx + radius + radius; | |
int cliph = dy + radius + radius; | |
if (stencil) | |
expandStencilClip(clipx, clipy, clipw, cliph); | |
expandActiveBounds(clipx, clipy, clipw, cliph); | |
// only use opStencil if not using regular stencil and we need blending | |
opStencil = !stencil && !(blendmode instanceof Overwrite); | |
do { | |
fillCircle(x1, y1, radius); | |
int e2 = 2 * err; | |
if (e2 > -dy) { | |
err = err - dy; | |
x1 = x1 + sx; | |
} | |
if (e2 < dx) { | |
err = err + dx; | |
y1 = y1 + sy; | |
} | |
} while (!(x1 == x2 && y1 == y2)); | |
if (opStencil) | |
flushBuffer(opStencilBuffer, clipx, clipy, clipw, cliph); | |
opStencil = false; | |
} | |
public void endStencil() { | |
boolean wasActive = stencil; | |
stencil = false; | |
if (wasActive) | |
flushBuffer(stencilBuffer, sclipx, sclipy, sclipw, scliph); | |
} | |
/** | |
* | |
*/ | |
// clipping done, also unrolled for cache | |
public void fillCircle(int x, int y, int radius) { | |
int clipx = x - radius; | |
int clipy = y - radius; | |
int clipw = radius * 2; | |
int cliph = clipw; | |
if (stencil) | |
expandStencilClip(clipx, clipy, clipw, cliph); | |
expandActiveBounds(clipx, clipy, clipw, cliph); | |
opStencil = !stencil && !(blendmode instanceof Overwrite); | |
int xOff = radius, yOff = 0; | |
int radiusError = 1 - xOff; | |
while (xOff >= yOff) { | |
//drawPixel(-yOff + x, -xOff + y); // north left top | |
//drawPixel(yOff + x, -xOff + y); // north right top | |
int xi = x - yOff; | |
int yi = (y - xOff) * w; | |
int start = Math.max(xi + yi, Math.max(yi, 0)); | |
int end = Math.min(xi + yi + yOff + yOff, Math.min(yi + w, buffer.length)); | |
for (int i = start; i < end; i++) | |
if (opStencil) | |
opStencilBuffer[i] = true; | |
else | |
drawPixel(i); | |
yOff++; | |
if (radiusError < 0) { | |
radiusError += 2 * yOff + 1; | |
} else { | |
xOff--; | |
radiusError += 2 * (yOff - xOff + 1); | |
} | |
} | |
xOff = radius; | |
yOff = 0; | |
radiusError = 1 - xOff; | |
while (xOff >= yOff) { | |
//drawPixel(-xOff + x, -yOff + y); // north left bottom | |
//drawPixel(xOff + x, -yOff + y); // north right bottom | |
int xi = x - xOff; | |
int yi = (y - yOff) * w; | |
int start = Math.max(xi + yi, Math.max(yi, 0)); | |
int end = Math.min(xi + yi + xOff + xOff, Math.min(yi + w, buffer.length)); | |
for (int i = start; i < end; i++) | |
if (opStencil) | |
opStencilBuffer[i] = true; | |
else | |
drawPixel(i); | |
yOff++; | |
if (radiusError < 0) { | |
radiusError += 2 * yOff + 1; | |
} else { | |
xOff--; | |
radiusError += 2 * (yOff - xOff + 1); | |
} | |
} | |
xOff = radius; | |
yOff = 0; | |
radiusError = 1 - xOff; | |
while (xOff >= yOff) { | |
//drawPixel(-xOff + x, yOff + y); // south left top | |
//drawPixel(xOff + x, yOff + y); // south right top | |
int xi = x - xOff; | |
int yi = (y + yOff - 1) * w; | |
int start = Math.max(xi + yi, Math.max(yi, 0)); | |
int end = Math.min(xi + yi + xOff + xOff, Math.min(yi + w, buffer.length)); | |
for (int i = start; i < end; i++) | |
if (opStencil) | |
opStencilBuffer[i] = true; | |
else | |
drawPixel(i); | |
yOff++; | |
if (radiusError < 0) { | |
radiusError += 2 * yOff + 1; | |
} else { | |
xOff--; | |
radiusError += 2 * (yOff - xOff + 1); | |
} | |
} | |
xOff = radius; | |
yOff = 0; | |
radiusError = 1 - xOff; | |
while (xOff >= yOff) { | |
//drawPixel(-yOff + x, xOff + y); // south left bottom | |
//drawPixel(yOff + x, xOff + y); // south right bottom | |
int xi = x - yOff; | |
int yi = (y + xOff - 1) * w; | |
int start = Math.max(xi + yi, Math.max(yi, 0)); | |
int end = Math.min(xi + yi + yOff + yOff, Math.min(yi + w, buffer.length)); | |
for (int i = start; i < end; i++) | |
if (opStencil) | |
opStencilBuffer[i] = true; | |
else | |
drawPixel(i); | |
yOff++; | |
if (radiusError < 0) { | |
radiusError += 2 * yOff + 1; | |
} else { | |
xOff--; | |
radiusError += 2 * (yOff - xOff + 1); | |
} | |
} | |
if (opStencil) | |
flushBuffer(opStencilBuffer, clipx, clipy, clipw, cliph); | |
opStencil = false; | |
} | |
public void fillEllipse(int x, int y, int width, int height) { | |
int yOff = 0; | |
int maxy = y + height; | |
double a = width / (double) height; | |
int ywOff = y * w; | |
x += width >> 1; | |
if (y < 0) { | |
if (y < -height) | |
return; | |
yOff -= y; | |
maxy -= y; | |
ywOff = 0; | |
} | |
maxy = Math.min(maxy, h) - y; | |
for (; yOff < maxy; yOff++, ywOff += w) { | |
int xOff = (int) (Math.sqrt(yOff * (height - yOff)) * a); | |
int start = Math.max(x - xOff, 0) + ywOff; | |
int end = Math.min(x + xOff, w) + ywOff; | |
for (int i = start; i < end; i++) | |
drawPixel(i); | |
} | |
} | |
public void fillRect(int x, int y, int width, int height) { | |
//TODO:* do clipping * | |
// single loop method: | |
/* | |
int start = x + y * w; | |
int end = start + (height - 1) * w + width; | |
int inc = w - width; | |
for (int i = start, t = 0; i < end; i++) { | |
drawPixel(i); | |
if (++t % width == 0) | |
i += inc; | |
} | |
*/ | |
// nested loop method, honestly the jit can probably optimize this better than single loop | |
int my = y + height; | |
int mx = x + width; | |
for (int yi = y; yi < my; yi++) { | |
int ii = yi * w; | |
for (int xi = x; xi < mx; xi++) { | |
drawPixel(ii + xi); | |
} | |
} | |
/* | |
for (; y < y + h; y++) { | |
drawLine(x, y, x + w, y); | |
} | |
*/ | |
} | |
public Rectangle getActiveBounds() { | |
return new Rectangle(abx, aby, abw, abh); | |
} | |
public Rectangle getActiveStencilBounds() { | |
return new Rectangle(sclipx, sclipy, sclipw, scliph); | |
} | |
public Color getColor() { | |
return MutableColor.getColor(cc); | |
} | |
public BufferedImage getImage() { | |
return image; | |
} | |
public void resetActiveBounds() { | |
abx = w; | |
aby = h; | |
abw = abh = 0; | |
} | |
public void setBackground(Color color) { | |
setBackground(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); | |
} | |
public void setBackground(int argb) { | |
int a = argb >> 24 & 0xFF; | |
int r = argb >> 16 & 0xFF; | |
int g = argb >> 8 & 0xFF; | |
int b = argb & 0xFF; | |
setBackground(r, g, b, a); | |
} | |
public void setBackground(int r, int g, int b) { | |
setBackground(r, g, b, 255); | |
} | |
public void setBackground(int r, int g, int b, int a) { | |
bgr = mult(r, a); | |
bgg = mult(g, a); | |
bgb = mult(b, a); | |
bga = a; | |
bgc = ColorUtils.pack(bgr, bgg, bgc, bga); | |
} | |
public void setBlendMode(BlendMode mode) { | |
blendmode = mode; | |
} | |
public void setColor(Color color) { // MutableColor works here | |
setColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); | |
} | |
public void setColor(int argb) { | |
int a = argb >> 24 & 0xFF; | |
int r = argb >> 16 & 0xFF; | |
int g = argb >> 8 & 0xFF; | |
int b = argb & 0xFF; | |
setColor(r, g, b, a); | |
} | |
public void setColor(int r, int g, int b) { | |
setColor(r, g, b, 255); | |
} | |
public void setColor(int r, int g, int b, int a) { | |
cr = mult(r, a); | |
cg = mult(g, a); | |
cb = mult(b, a); | |
crp = (cr << 16) - 0xFF0000; | |
cgp = (cg << 8) - 0x00FF00; | |
cbp = cb - 0x0000FF; | |
ca = a; | |
cc = ColorUtils.pack(cr, cg, cb, ca); | |
} | |
/** | |
* Overwrites the pixel at (x, y) to the current color. | |
*/ | |
public void setPixel(int x, int y) { | |
setPixel(x, y, cc); | |
} | |
public void setPixel(int x, int y, int value) { | |
setPixelAtIndex(x + y * w, value); | |
} | |
// clamps to 255 | |
private int clamp(int x) { | |
x -= 0xFF; | |
x &= x >> 8; | |
x += 0xFF; | |
return x; | |
} | |
/** | |
* interesting unsigned-byte-ish multiply | |
* integer equivalent of | |
* | |
* C = A * B where A, B are both in the range [0, 1] | |
* | |
* but instead it's in the range [0, 255] | |
* | |
* Examples: | |
* mult(255, 255) = 255 // 1.0 * 1.0 = 1.0 | |
* mult(255, 128) = 128 // 1.0 * 0.5 = 0.5 | |
* mult(128, 128) = 64 // 0.5 * 0.5 = 0.25 | |
*/ | |
private int mult(int a, int b) { | |
a = b * a + 0x80; | |
return (a >> 8) + a >> 8; | |
} | |
/** | |
* Clears the stencil buffer. | |
*/ | |
protected void clearStencilBuffer() { | |
Arrays.fill(stencilBuffer, false); | |
} | |
protected void drawPixel(int i) { | |
if (stencil) | |
stencilBuffer[i] = true; | |
else { | |
final int src = buffer[i]; | |
/*// get channels | |
int a = src & 0xFF000000; | |
int r = src & 0x00FF0000; | |
int g = src & 0x0000FF00; | |
int b = src & 0x000000FF; | |
// add src and current color (premultiplied with alpha), then clamp | |
r += crp; | |
g += cgp; | |
b += cbp; | |
r &= r >> 8; | |
g &= g >> 8; | |
b &= b >> 8; | |
r += 0xFF0000; | |
g += 0x00FF00; | |
b += 0x0000FF; | |
// store | |
buffer[i] = a | r | g | b; | |
*/ | |
buffer[i] = blendmode.blend(src, cc); | |
} | |
} | |
/** | |
* Flushes entire stencil to main buffer. | |
*/ | |
protected void drawStencilBuffer() { | |
if (!stencil) | |
return; | |
stencil = false; | |
for (int i = 0; i < stencilBuffer.length; i++) { | |
if (stencilBuffer[i]) { | |
drawPixel(i); | |
} | |
} | |
stencil = true; | |
} | |
protected void expandActiveBounds(int x, int y, int w, int h) { | |
abx = Math.min(abx, x); | |
aby = Math.min(aby, y); | |
abw = Math.max(abw, w); | |
abh = Math.max(abh, h); | |
} | |
protected void expandStencilClip(int x, int y, int w, int h) { | |
sclipx = Math.min(sclipx, x); | |
sclipy = Math.min(sclipy, y); | |
sclipw = Math.max(sclipw, w); | |
scliph = Math.max(scliph, h); | |
} | |
protected void flushBuffer(final boolean[] buffer, int x, int y, int width, int height) { | |
if (x < 0) { | |
if (x <= -width) | |
return; | |
width += x; | |
x = 0; | |
} else if (x >= w - width) { | |
if (x >= w) | |
return; | |
width = Math.min(width + x, w); | |
width -= x; | |
} | |
if (y < 0) { | |
if (y <= -height) | |
return; | |
height += y; | |
y = 0; | |
} else if (y >= h - height) { | |
if (y >= h) | |
return; | |
height = Math.min(height + y, h); | |
height -= y; | |
} | |
width += x; | |
height += y; | |
width = Math.min(width, w); | |
height = Math.min(height, h); | |
for (; y < height; y++) { | |
for (int xi = x; xi < width; xi++) { | |
int i = xi + y * w; | |
if (buffer[i]) { | |
drawPixel(i); | |
buffer[i] = false; | |
} | |
} | |
} | |
} | |
/** | |
* Flushes entire stencil to main buffer and clears the stencil buffer. | |
*/ | |
protected void flushStencilBuffer() { | |
if (!stencil) | |
return; | |
stencil = false; | |
for (int i = 0; i < stencilBuffer.length; i++) { | |
if (stencilBuffer[i]) { | |
drawPixel(i); | |
stencilBuffer[i] = false; | |
} | |
} | |
stencil = true; | |
} | |
protected void setPixelAtIndex(int i, int value) { | |
buffer[i] = value; | |
} | |
protected void stencilPixel(int i) { | |
(opStencil ? opStencilBuffer : stencilBuffer)[i] = true; | |
} | |
} |
This file contains 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
public interface BlendMode { | |
public int blend(int argb1, int argb2); | |
} | |
public class Overwrite implements BlendMode { | |
@Override | |
public int blend(int argb1, int argb2) { | |
return argb2; | |
} | |
} | |
public enum MathUtils { | |
; | |
public static int clamp(int i, int max, int min) { | |
return Math.min(Math.max(i, min), max); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment