/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.canvas; import static loon.canvas.Limit.*; import loon.LSystem; import loon.geom.RectBox; import loon.geom.RectF; import loon.geom.Shape; import loon.utils.CollectionUtils; import loon.utils.MathUtils; public abstract class PixmapFImpl { protected final static int def_skip = 2; protected final static int def_skip_html5 = 8; private RectF temp_rect = new RectF(); private int _skip = def_skip; private RectF _clip = new RectF(); private float _translateX = 0, _translateY = 0; private float _width, _height; public PixmapFImpl(float tx, float ty, RectF clip, float w, float h, int skip) { setClipImpl(tx, ty, clip, w, h, skip); } public PixmapFImpl(float tx, float ty, RectBox clip, float w, float h, int skip) { setClipImpl(tx, ty, clip, w, h, skip); } protected void setClipImpl(float tx, float ty, RectF clip, float w, float h) { this.setClipImpl(tx, ty, clip, w, h, _skip); } protected void setClipImpl(float tx, float ty, RectBox clip, float w, float h) { this.setClipImpl(tx, ty, clip, w, h, _skip); } protected void setClipImpl(float tx, float ty, RectBox clip, float w, float h, int skip) { this._translateX = tx; this._translateY = ty; this._clip.x = clip.x; this._clip.y = clip.y; this._clip.width = clip.width; this._clip.height = clip.height; this._width = w; this._height = h; this._skip = skip; if (LSystem.isHTML5() && _skip < def_skip_html5) { _skip = def_skip_html5; } else if (_skip < 1) { _skip = 1; } } protected void setClipImpl(float tx, float ty, RectF clip, float w, float h, int skip) { this._translateX = tx; this._translateY = ty; this._clip.set(clip); this._width = w; this._height = h; this._width = w; this._height = h; this._skip = skip; if (LSystem.isHTML5() && _skip < def_skip_html5) { _skip = def_skip_html5; } else if (_skip < 1) { _skip = 1; } } public int getPixSkip() { return _skip; } public void setPixSkip(int s) { _skip = s; } protected void fillPolygonImpl(float[] xPoints, float[] yPoints, int nPoints) { float[] xPointsCopy; if (_translateX == 0) { xPointsCopy = xPoints; } else { xPointsCopy = CollectionUtils.copyOf(xPoints); for (int i = 0; i < nPoints; i++) { xPointsCopy[i] += _translateX; } } float[] yPointsCopy; if (_translateY == 0) { yPointsCopy = yPoints; } else { yPointsCopy = CollectionUtils.copyOf(yPoints); for (int i = 0; i < nPoints; i++) { yPointsCopy[i] += _translateY; } } RectF bounds = RectF.getIntersection( setBoundingBox(temp_rect, xPointsCopy, yPointsCopy, nPoints), _clip, temp_rect); for (float x = bounds.x; x < bounds.x + bounds.width; x += _skip) { for (float y = bounds.y; y < bounds.y + bounds.height; y += _skip) { if (contains(xPointsCopy, yPointsCopy, nPoints, bounds, x, y)) { drawPointImpl(x, y); } } } } protected void drawLineImpl(float x1, float x2, float y) { if (y >= _clip.y && y < _clip.y + _clip.height) { y *= _width; float maxX = MathUtils.min(x2, _clip.x + _clip.width - 1); for (int x = (int) MathUtils.max(x1, _clip.x); x <= maxX; x += _skip) { drawPointImpl(x, (x + y) / _width); } } } protected void drawOvalImpl(float x, float y, float width, float height) { drawCircleImpl(x, y, width, height, false, new CircleUpdate() { public void newPoint(float xLeft, float yTop, float xRight, float yBottom) { drawPointImpl(xLeft, yTop); drawPointImpl(xRight, yTop); drawPointImpl(xLeft, yBottom); drawPointImpl(xRight, yBottom); } }); } protected void drawPolygonImpl(float xPoints[], float yPoints[], int nPoints) { drawPolylineImpl(xPoints, yPoints, nPoints); drawLineImpl(xPoints[nPoints - 1], yPoints[nPoints - 1], xPoints[0], yPoints[0]); } protected void drawPolylineImpl(float xPoints[], float yPoints[], float nPoints) { for (int i = 1; i < nPoints; i++) { drawLineImpl(xPoints[i - 1], yPoints[i - 1], xPoints[i], yPoints[i]); } } protected void fillOvalImpl(float x, float y, float width, float height) { drawCircleImpl(x, y, width, height, true, new CircleUpdate() { public void newPoint(float xLeft, float yTop, float xRight, float yBottom) { drawLineImpl(xLeft, xRight, yTop); if (yTop != yBottom) { drawLineImpl(xLeft, xRight, yBottom); } } }); } protected void drawVerticalLineImpl(float x, float y1, float y2) { if (x >= _clip.x && x < _clip.x + _clip.width) { int maxY = (int) (MathUtils.min(y2, _clip.y + _clip.height - 1) * _width); for (int y = (int) (MathUtils.max(y1, _clip.y) * _width); y <= maxY; y += _width + _skip) { drawPointImpl(x, (x + y) / _width); } } } protected void drawLineImpl(float x1, float y1, float x2, float y2) { x1 += _translateX; y1 += _translateY; x2 += _translateX; y2 += _translateY; float dx = x2 - x1; float dy = y2 - y1; if (dx == 0) { if (y1 < y2) { drawVerticalLineImpl(x1, y1, y2); } else { drawVerticalLineImpl(x1, y2, y1); } } else if (dy == 0) { if (x1 < x2) { drawLineImpl(x1, x2, y1); } else { drawLineImpl(x2, x1, y1); } } else { boolean swapXY = false; int dxNeg = 1; int dyNeg = 1; boolean negativeSlope = false; if (MathUtils.abs(dy) > MathUtils.abs(dx)) { float temp = x1; x1 = y1; y1 = temp; temp = x2; x2 = y2; y2 = temp; dx = x2 - x1; dy = y2 - y1; swapXY = true; } if (x1 > x2) { float temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; dx = x2 - x1; dy = y2 - y1; } if (dy * dx < 0) { if (dy < 0) { dyNeg = -1; dxNeg = 1; } else { dyNeg = 1; dxNeg = -1; } negativeSlope = true; } float d = 2 * (dy * dyNeg) - (dx * dxNeg); float incrH = 2 * dy * dyNeg; float incrHV = 2 * ((dy * dyNeg) - (dx * dxNeg)); float x = x1; float y = y1; float tempX = x; float tempY = y; if (swapXY) { float temp = x; x = y; y = temp; } drawPointImpl(x, y); x = tempX; y = tempY; while (x < x2) { if (d <= 0) { x++; d += incrH; } else { d += incrHV; x++; if (!negativeSlope) { y++; } else { y--; } } tempX = x; tempY = y; if (swapXY) { float temp = x; x = y; y = temp; } drawPointImpl(x, y); x = tempX; y = tempY; } } } protected void drawArcImpl(float x, float y, float width, float height, float start, float arcAngle) { if (arcAngle == 0) { return; } if (arcAngle < 0) { start = 360 - arcAngle; arcAngle = 360 + arcAngle; } start %= 360; if (start < 0) { start += 360; } if (arcAngle % 360 == 0) { drawOvalImpl(x, y, width, height); return; } else { arcAngle %= 360; } final float startAngle = arcAngle > 0 ? start : (start + arcAngle < 0 ? start + arcAngle + 360 : start + arcAngle); final float centerX = x + _translateX + width / 2; final float centerY = y + _translateY + height / 2; final float xPoints[] = new float[7]; final float yPoints[] = new float[7]; final int nPoints = getBoundingShape(xPoints, yPoints, startAngle, MathUtils.abs(arcAngle), centerX, centerY, x + _translateX - 1, y + _translateY - 1, width + 2, height + 2); final RectF bounds = RectF.getIntersection( setBoundingBox(temp_rect, xPoints, yPoints, nPoints), _clip, temp_rect); this.drawCircleImpl(x, y, width, height, false, new CircleUpdate() { public void newPoint(float xLeft, float yTop, float xRight, float yBottom) { drawArcPointImpl(xPoints, yPoints, nPoints, bounds, xLeft, yTop); drawArcPointImpl(xPoints, yPoints, nPoints, bounds, xRight, yTop); drawArcPointImpl(xPoints, yPoints, nPoints, bounds, xLeft, yBottom); drawArcPointImpl(xPoints, yPoints, nPoints, bounds, xRight, yBottom); } }); } protected void drawArcPointImpl(float[] xPoints, float[] yPoints, int nPoints, RectF bounds, float x, float y) { if (contains(xPoints, yPoints, nPoints, bounds, x, y)) { drawPointImpl(x, y); } } protected void fillArcImpl(float x, float y, float width, float height, float start, float arcAngle) { if (arcAngle == 0) { return; } if (arcAngle < 0) { start = 360 - arcAngle; arcAngle = 360 + arcAngle; } start %= 360; if (start < 0) { start += 360; } if (arcAngle % 360 == 0) { fillOvalImpl(x, y, width, height); return; } else { arcAngle %= 360; } final float startAngle = arcAngle > 0 ? start : (start + arcAngle < 0 ? start + arcAngle + 360 : start + arcAngle); final float centerX = x + _translateX + width / 2; final float centerY = y + _translateY + height / 2; final float xPoints[] = new float[7]; final float yPoints[] = new float[7]; final int nPoints = getBoundingShape(xPoints, yPoints, startAngle, MathUtils.abs(arcAngle), centerX, centerY, x + _translateX - 1, y + _translateY - 1, width + 2, height + 2); final RectF bounds = setBoundingBox(temp_rect, xPoints, yPoints, nPoints); this.drawCircleImpl(x, y, width, height, true, new CircleUpdate() { public void newPoint(float xLeft, float yTop, float xRight, float yBottom) { drawArcImpl(xPoints, yPoints, nPoints, bounds, xLeft, xRight, yTop); if (yTop != yBottom) { drawArcImpl(xPoints, yPoints, nPoints, bounds, xLeft, xRight, yBottom); } } }); } protected void drawArcImpl(float[] xPoints, float[] yPoints, int nPoints, RectF bounds, float xLeft, float xRight, float y) { if (y >= _clip.y && y < _clip.y + _clip.height) { for (int x = (int) MathUtils.max(xLeft, _clip.x); x <= xRight; x += _skip) { if (contains(xPoints, yPoints, nPoints, bounds, x, y)) { drawPointImpl(x, y); } } } } protected void drawCircleImpl(float x, float y, float width, float height, boolean fill, CircleUpdate listener) { float a = width / 2; float b = height / 2; float squareA = (width * width / 4); float squareB = (height * height / 4); float squareAB = MathUtils.round(width * width * height * height, 16L); x += _translateX; y += _translateY; float centerX = x + a; float centerY = y + b; int deltaX = (width % 2 == 0) ? 0 : 1; int deltaY = (height % 2 == 0) ? 0 : 1; float currentY = b; float currentX = 0; float lastx1 = centerX - currentX; float lastx2 = centerX + currentX + deltaX; float lasty1 = centerY - currentY; float lasty2 = centerY + currentY + deltaY; while (currentX <= a && currentY >= 0) { float deltaA = (currentX + 1) * (currentX + 1) * squareB + currentY * currentY * squareA - squareAB; float deltaB = (currentX + 1) * (currentX + 1) * squareB + (currentY - 1) * (currentY - 1) * squareA - squareAB; float deltaC = currentX * currentX * squareB + (currentY - 1) * (currentY - 1) * squareA - squareAB; if (deltaA <= 0) { currentX++; } else if (deltaC >= 0) { currentY--; } else { float min = MathUtils.min( MathUtils.abs(deltaA), MathUtils.min(MathUtils.abs(deltaB), MathUtils.abs(deltaC))); if (min == MathUtils.abs(deltaA)) { currentX++; } else if (min == MathUtils.abs(deltaC)) { currentY--; } else { currentX++; currentY--; } } float x1 = centerX - currentX; float x2 = centerX + currentX + deltaX; float y1 = centerY - currentY; float y2 = centerY + currentY + deltaY; if (!fill || lasty1 != y1) { listener.newPoint(lastx1, lasty1, lastx2, lasty2); lasty1 = y1; lasty2 = y2; } lastx1 = x1; lastx2 = x2; } if (lasty1 < lasty2) { for (; lasty1 <= lasty2; lasty1++, lasty2--) { listener.newPoint(centerX - a, lasty1, centerX + a + deltaX, lasty2); } } } protected void drawShapeImpl(Shape shape, float x1, float y1) { if (shape == null) { return; } final float[] points = shape.getPoints(); int size = points.length; int len = size / 2; final float[] xps = new float[len]; final float[] yps = new float[len]; for (int i = 0, j = 0; i < size; i += 2, j++) { xps[j] = points[i] + x1; yps[j] = points[i + 1] + y1; } drawPolylineImpl(xps, yps, len); int length = len - 1; if (xps.length > 0 && length < xps.length) { drawLineImpl(xps[length], yps[length], xps[0], yps[0]); } } protected void fillShapeImpl(Shape shape, float x1, float y1) { if (shape == null) { return; } final float[] points = shape.getPoints(); int size = points.length; int len = size / 2; final float[] xps = new float[len]; final float[] yps = new float[len]; for (int i = 0, j = 0; i < size; i += 2, j++) { xps[j] = points[i] + x1; yps[j] = points[i + 1] + y1; } RectF bounds = RectF.getIntersection( setBoundingBox(temp_rect, xps, yps, len), _clip, temp_rect); for (float x = bounds.x; x < bounds.x + bounds.width; x += _skip) { for (float y = bounds.y; y < bounds.y + bounds.height; y += _skip) { if (contains(xps, yps, len, bounds, x, y)) { drawPointNative(x, y, _skip); } } } } protected boolean inside(float x, float y) { return (x < _clip.x || x >= _clip.x + _clip.width || y < _clip.y || y >= _clip.y + _clip.height); } protected void drawRectImpl(float x1, float y1, float w1, float h1) { float tempX = x1; float tempY = y1; float tempWidth = w1; float tempHeight = h1; drawLineImpl(tempX, tempY, tempX + tempWidth, tempY); drawLineImpl(tempX + tempWidth, tempY, tempX + tempWidth, tempY + tempHeight); drawLineImpl(tempX + tempWidth, tempY + tempHeight, tempX, tempY + tempHeight); drawLineImpl(tempX, tempY + tempHeight, tempX, tempY); } protected void drawRoundRectImpl(float x, float y, float width, float height, float arcWidth, float arcHeight) { drawLineImpl(x + arcWidth / 2, y, x + width - arcWidth / 2, y); drawLineImpl(x, y + arcHeight / 2, x, y + height - arcHeight / 2); drawLineImpl(x + arcWidth / 2, y + height, x + width - arcWidth / 2, y + height); drawLineImpl(x + width, y + arcHeight / 2, x + width, y + height - arcHeight / 2); drawArcImpl(x, y, arcWidth, arcHeight, 90, 90); drawArcImpl(x + width - arcWidth, y, arcWidth, arcHeight, 0, 90); drawArcImpl(x, y + height + -arcHeight, arcWidth, arcHeight, 180, 90); drawArcImpl(x + width - arcWidth, y + height + -arcHeight, arcWidth, arcHeight, 270, 90); } protected void fillRoundRectImpl(float x, float y, float width, float height, float arcWidth, float arcHeight) { float w = width - arcWidth; float h = height - arcHeight; if (w > 0 && h > 0) { fillRectNative(x + arcWidth / 2, y, w, height); fillRectNative(x, y + arcHeight / 2 - 1, arcWidth / 2, h); fillRectNative(x + width - arcWidth / 2, y + arcHeight / 2 - 1, arcWidth / 2, height - arcHeight); } fillArcImpl(x + 1, y, arcWidth - 1, arcHeight - 1, 90, 90); fillArcImpl(x + width - arcWidth - 1, y, arcWidth - 1, arcHeight - 1, 0, 90); fillArcImpl(x + 1, y + height + -arcHeight, arcWidth - 1, arcHeight - 1, 180, 90); fillArcImpl(x + width - arcWidth - 1, y + height + -arcHeight, arcWidth - 1, arcHeight - 1, 270, 90); } protected final void drawRoundRectImpl(float x, float y, float width, float height, float radius) { if (radius < 0) { throw new IllegalArgumentException("radius > 0"); } if (radius == 0) { drawRectImpl(x, y, width, height); return; } float mr = MathUtils.min(width, height) / 2; if (radius > mr) { radius = mr; } drawLineImpl(x + radius, y, x + width - radius, y); drawLineImpl(x, y + radius, x, y + height - radius); drawLineImpl(x + width, y + radius, x + width, y + height - radius); drawLineImpl(x + radius, y + height, x + width - radius, y + height); float d = radius * 2; drawArcImpl(x + width - d, y + height - d, d, d, 0, 90); drawArcImpl(x, y + height - d, d, d, 90, 180); drawArcImpl(x + width - d, y, d, d, 270, 360); drawArcImpl(x, y, d, d, 180, 270); return; } protected void fillRoundRectImpl(float x, float y, float width, float height, float radius) { if (radius < 0) { throw new IllegalArgumentException("radius > 0"); } if (radius == 0) { fillRectNative(x, y, width, height); return; } float mr = MathUtils.min(width, height) / 2; if (radius > mr) { radius = mr; } float d = radius * 2; float w = width - d; float h = height - d; if (w > 0 && h > 0) { fillRectNative(x + radius, y, w, radius); fillRectNative(x, y + radius, radius, h); fillRectNative(x + width - radius, y + radius, radius, h); fillRectNative(x + radius, y + height - radius, w, radius); fillRectNative(x + radius, y + radius, w, h); } fillArcImpl(x + width - d, y + height - d, d, d, 0, 90); fillArcImpl(x, y + height - d, d, d, 90, 180); fillArcImpl(x + width - d, y, d, d, 270, 360); fillArcImpl(x, y, d, d, 180, 270); } protected void drawPointImpl(float x, float y) { if (_skip > 4) { int loc = _skip / 2; drawPointNative(x - loc - 4, y - loc - 4, _skip); } else { drawPointNative(x, y, _skip); } } protected abstract void drawPointNative(float x, float y, int skip); protected abstract void fillRectNative(float x, float y, float width, float height); private interface CircleUpdate { public void newPoint(float xLeft, float yTop, float xRight, float yBottom); } public float getWidthImpl() { return _width; } public float getHeightImpl() { return _height; } }