mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 19:04:39 -05:00
Implement Bitmap.copy, text layouting (#1455)
* Bitmap: Use provided config * Bitmap: implement copy * Bitmap: Simplify getPixels This also fixes a bug where the returned data may not be in the correct format Android getPixels(): > The returned colors are non-premultiplied ARGB values in the sRGB color space. BufferedImage getRGB(): > Returns an array of integer pixels in the default RGB color model (TYPE_INT_ARGB) and default sRGB color space * Stub TextPaint and Paint * Paint: Implement some required functions * Stub StaticLayout and Layout * Implement some Paint support * Draw Bounds * WebP write support * First text rendering * Paint: Fix text size, font metrics * Paint: Fix not copying new properties Fixes font size in draw * Canvas: Stroke add cap/join for better aliasing Otherwise we get bad artifacts on sharp corners Based on https://stackoverflow.com/a/35222059/ * Remove logs * Canvas: Implement other drawText methods * Bitmap: support erase * Layout: Fix text direction Should be LTR, otherwise 0 is read, which is automatically interpreted as RTL without explicit check * Bitmap: scale to destination rectangle * Canvas: drawBitmap with just x/y * Bitmap: Convert image on JPEG export to RGB JPEG does not support alpha, so will throw "bogus color space" * Switch to newer fork
This commit is contained in:
@@ -2,17 +2,17 @@ package android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public final class Bitmap {
|
||||
private final int width;
|
||||
@@ -75,6 +75,19 @@ public final class Bitmap {
|
||||
}
|
||||
}
|
||||
|
||||
private static int configToBufferedImageType(Config config) {
|
||||
switch (config) {
|
||||
case ALPHA_8:
|
||||
return BufferedImage.TYPE_BYTE_GRAY;
|
||||
case RGB_565:
|
||||
return BufferedImage.TYPE_USHORT_565_RGB;
|
||||
case ARGB_8888:
|
||||
return BufferedImage.TYPE_INT_ARGB;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for checking that x and y are >= 0
|
||||
*
|
||||
@@ -106,7 +119,7 @@ public final class Bitmap {
|
||||
}
|
||||
|
||||
public static Bitmap createBitmap(int width, int height, Config config) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage image = new BufferedImage(width, height, configToBufferedImageType(config));
|
||||
return new Bitmap(image);
|
||||
}
|
||||
|
||||
@@ -144,8 +157,10 @@ public final class Bitmap {
|
||||
formatString = "png";
|
||||
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||
formatString = "jpg";
|
||||
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||
formatString = "webp";
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported compression format!");
|
||||
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||
}
|
||||
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||
@@ -162,14 +177,19 @@ public final class Bitmap {
|
||||
}
|
||||
writer.setOutput(ios);
|
||||
|
||||
BufferedImage img = image;
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
if ("jpg".equals(formatString)) {
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
param.setCompressionQuality(qualityFloat);
|
||||
|
||||
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
img.getGraphics().drawImage(image, 0, 0, null);
|
||||
}
|
||||
|
||||
try {
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
writer.write(null, new IIOImage(img, null, null), param);
|
||||
ios.close();
|
||||
writer.dispose();
|
||||
} catch (IOException ex) {
|
||||
@@ -179,6 +199,12 @@ public final class Bitmap {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Bitmap copy(Config config, boolean isMutable) {
|
||||
Bitmap ret = createBitmap(width, height, config);
|
||||
ret.image.getGraphics().drawImage(image, 0, 0, null);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared code to check for illegal arguments passed to getPixels()
|
||||
* or setPixels()
|
||||
@@ -224,12 +250,18 @@ public final class Bitmap {
|
||||
int x, int y, int width, int height) {
|
||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||
|
||||
Raster raster = image.getData();
|
||||
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
|
||||
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||
}
|
||||
|
||||
for (int ht = 0; ht < height; ht++) {
|
||||
int rowOffset = offset + stride * ht;
|
||||
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
|
||||
}
|
||||
public void eraseColor(int c) {
|
||||
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setColor(color);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,184 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public final class Canvas {
|
||||
private BufferedImage canvasImage;
|
||||
private Graphics2D canvas;
|
||||
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||
|
||||
private static final String TAG = "Canvas";
|
||||
|
||||
public Canvas(Bitmap bitmap) {
|
||||
canvasImage = bitmap.getImage();
|
||||
canvas = canvasImage.createGraphics();
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
||||
canvas.drawImage(sourceImageCropped, null, dst.left, dst.top);
|
||||
canvas.drawImage(sourceImageCropped, dst.left, dst.top, dst.getWidth(), dst.getHeight(), null);
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, float left, float top, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
canvas.drawImage(sourceImage, null, (int) left, (int) top);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(new String(text, index, count), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
|
||||
applyPaint(paint);
|
||||
GlyphVector glyphVector = paint.getFont().createGlyphVector(canvas.getFontRenderContext(), text);
|
||||
Shape textShape = glyphVector.getOutline();
|
||||
switch (paint.getStyle()) {
|
||||
case Paint.Style.FILL:
|
||||
canvas.drawString(text, x, y);
|
||||
break;
|
||||
case Paint.Style.STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
restore();
|
||||
break;
|
||||
case Paint.Style.FILL_AND_STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
canvas.fill(textShape);
|
||||
restore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(text.substring(start, end), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
String str = text.subSequence(start, end).toString();
|
||||
drawText(str, x, y, paint);
|
||||
}
|
||||
|
||||
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
|
||||
@NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void translate(float dx, float dy) {
|
||||
if (dx == 0.0f && dy == 0.0f) return;
|
||||
// TODO: check this, should translations stack?
|
||||
canvas.translate(dx, dy);
|
||||
}
|
||||
|
||||
public void scale(float sx, float sy) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
canvas.scale(sx, sy);
|
||||
}
|
||||
|
||||
public final void scale(float sx, float sy, float px, float py) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
translate(px, py);
|
||||
scale(sx, sy);
|
||||
translate(-px, -py);
|
||||
}
|
||||
|
||||
public void rotate(float degrees) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees);
|
||||
}
|
||||
|
||||
public final void rotate(float degrees, float px, float py) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees, px, py);
|
||||
}
|
||||
|
||||
public int getSaveCount() {
|
||||
return transformStack.size();
|
||||
}
|
||||
|
||||
public int save() {
|
||||
transformStack.add(canvas.getTransform());
|
||||
return getSaveCount();
|
||||
}
|
||||
|
||||
public void restoreToCount(int saveCount) {
|
||||
if (saveCount < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Underflow in restoreToCount - more restores than saves");
|
||||
}
|
||||
if (saveCount > getSaveCount()) {
|
||||
throw new IllegalArgumentException("Overflow in restoreToCount");
|
||||
|
||||
}
|
||||
AffineTransform ts = transformStack.get(saveCount - 1);
|
||||
canvas.setTransform(ts);
|
||||
while (transformStack.size() >= saveCount) {
|
||||
transformStack.remove(transformStack.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
restoreToCount(getSaveCount());
|
||||
}
|
||||
|
||||
public boolean getClipBounds(@NonNull Rect bounds) {
|
||||
Rectangle r = canvas.getClipBounds();
|
||||
if (r == null) {
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.right = canvasImage.getWidth();
|
||||
bounds.bottom = canvasImage.getHeight();
|
||||
return true;
|
||||
}
|
||||
bounds.left = r.x;
|
||||
bounds.top = r.y;
|
||||
bounds.right = r.x + r.width;
|
||||
bounds.bottom = r.y + r.height;
|
||||
return r.width != 0 && r.height != 0;
|
||||
}
|
||||
|
||||
private void applyPaint(Paint paint) {
|
||||
canvas.setFont(paint.getFont());
|
||||
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||
canvas.setColor(color);
|
||||
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
if (paint.isAntiAlias()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
}
|
||||
if (paint.isDither()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
}
|
||||
// TODO: use more from paint?
|
||||
}
|
||||
}
|
||||
|
||||
578
AndroidCompat/src/main/java/android/graphics/Color.java
Normal file
578
AndroidCompat/src/main/java/android/graphics/Color.java
Normal file
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.ColorLong;
|
||||
import android.annotation.HalfFloat;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.Size;
|
||||
import android.util.Half;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class Color {
|
||||
@ColorInt public static final int BLACK = 0xFF000000;
|
||||
@ColorInt public static final int DKGRAY = 0xFF444444;
|
||||
@ColorInt public static final int GRAY = 0xFF888888;
|
||||
@ColorInt public static final int LTGRAY = 0xFFCCCCCC;
|
||||
@ColorInt public static final int WHITE = 0xFFFFFFFF;
|
||||
@ColorInt public static final int RED = 0xFFFF0000;
|
||||
@ColorInt public static final int GREEN = 0xFF00FF00;
|
||||
@ColorInt public static final int BLUE = 0xFF0000FF;
|
||||
@ColorInt public static final int YELLOW = 0xFFFFFF00;
|
||||
@ColorInt public static final int CYAN = 0xFF00FFFF;
|
||||
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
|
||||
@ColorInt public static final int TRANSPARENT = 0;
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
private final float[] mComponents;
|
||||
|
||||
@NonNull
|
||||
private final ColorSpace mColorSpace;
|
||||
|
||||
public Color() {
|
||||
// This constructor is required for compatibility with previous APIs
|
||||
mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a) {
|
||||
this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = new float[] { r, g, b, a };
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = components;
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
public java.awt.Color toJavaColor() {
|
||||
return new java.awt.Color(red(), green(), blue(), alpha());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ColorSpace getColorSpace() {
|
||||
return mColorSpace;
|
||||
}
|
||||
|
||||
public ColorSpace.Model getModel() {
|
||||
return mColorSpace.getModel();
|
||||
}
|
||||
|
||||
public boolean isWideGamut() {
|
||||
return getColorSpace().isWideGamut();
|
||||
}
|
||||
|
||||
public boolean isSrgb() {
|
||||
return getColorSpace().isSrgb();
|
||||
}
|
||||
|
||||
@IntRange(from = 4, to = 5)
|
||||
public int getComponentCount() {
|
||||
return mColorSpace.getComponentCount() + 1;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public long pack() {
|
||||
return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Color convert(@NonNull ColorSpace colorSpace) {
|
||||
ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
connector.transform(color);
|
||||
return new Color(color, colorSpace);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public int toArgb() {
|
||||
if (mColorSpace.isSrgb()) {
|
||||
return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (mComponents[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
// The transformation saturates the output
|
||||
ColorSpace.connect(mColorSpace).transform(color);
|
||||
|
||||
return ((int) (color[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (color[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (color[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (color[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public float red() {
|
||||
return mComponents[0];
|
||||
}
|
||||
|
||||
public float green() {
|
||||
return mComponents[1];
|
||||
}
|
||||
|
||||
public float blue() {
|
||||
return mComponents[2];
|
||||
}
|
||||
|
||||
public float alpha() {
|
||||
return mComponents[mComponents.length - 1];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
public float[] getComponents() {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4)
|
||||
public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
|
||||
if (components == null) {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
if (components.length < mComponents.length) {
|
||||
throw new IllegalArgumentException("The specified array's length must be at "
|
||||
+ "least " + mComponents.length);
|
||||
}
|
||||
|
||||
System.arraycopy(mComponents, 0, components, 0, mComponents.length);
|
||||
return components;
|
||||
}
|
||||
|
||||
public float getComponent(@IntRange(from = 0, to = 4) int component) {
|
||||
return mComponents[component];
|
||||
}
|
||||
|
||||
public float luminance() {
|
||||
if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + mColorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(mComponents[0]);
|
||||
double g = eotf.applyAsDouble(mComponents[1]);
|
||||
double b = eotf.applyAsDouble(mComponents[2]);
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Color color = (Color) o;
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (!Arrays.equals(mComponents, color.mComponents)) return false;
|
||||
return mColorSpace.equals(color.mColorSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(mComponents);
|
||||
result = 31 * result + mColorSpace.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder("Color(");
|
||||
for (float c : mComponents) {
|
||||
b.append(c).append(", ");
|
||||
}
|
||||
b.append(mColorSpace.getName());
|
||||
b.append(')');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ColorSpace colorSpace(@ColorLong long color) {
|
||||
return ColorSpace.get((int) (color & 0x3fL));
|
||||
}
|
||||
|
||||
public static float red(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 48) & 0xffff));
|
||||
}
|
||||
|
||||
public static float green(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 32) & 0xffff));
|
||||
}
|
||||
|
||||
public static float blue(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 16) & 0xffff));
|
||||
}
|
||||
|
||||
public static float alpha(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
|
||||
return ((color >> 6) & 0x3ff) / 1023.0f;
|
||||
}
|
||||
|
||||
public static boolean isSrgb(@ColorLong long color) {
|
||||
return colorSpace(color).isSrgb();
|
||||
}
|
||||
|
||||
public static boolean isWideGamut(@ColorLong long color) {
|
||||
return colorSpace(color).isWideGamut();
|
||||
}
|
||||
|
||||
public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
return (int) (color & 0x3fL) == colorSpace.getId();
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int toArgb(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return (int) (color >> 32);
|
||||
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
|
||||
// The transformation saturates the output
|
||||
float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
|
||||
|
||||
return ((int) (a * 255.0f + 0.5f) << 24) |
|
||||
((int) (c[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (c[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (c[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorInt int color) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorLong long color) {
|
||||
return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b) {
|
||||
return new Color(r, g, b, 1.0f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a) {
|
||||
return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException("The specified color space must use a color model " +
|
||||
"with at most 3 color components");
|
||||
}
|
||||
return new Color(r, g, b, a, colorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (components.length < colorSpace.getComponentCount() + 1) {
|
||||
throw new IllegalArgumentException("Received a component array of length " +
|
||||
components.length + " but the color model requires " +
|
||||
(colorSpace.getComponentCount() + 1) + " (including alpha)");
|
||||
}
|
||||
return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(@ColorInt int color) {
|
||||
return (color & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue) {
|
||||
return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha) {
|
||||
return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.isSrgb()) {
|
||||
int argb =
|
||||
((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
return (argb & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
int id = colorSpace.getId();
|
||||
if (id == ColorSpace.MIN_ID) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown color space, please use a color space returned by ColorSpace.get()");
|
||||
}
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"The color space must use a color model with at most 3 components");
|
||||
}
|
||||
|
||||
@HalfFloat short r = Half.toHalf(red);
|
||||
@HalfFloat short g = Half.toHalf(green);
|
||||
@HalfFloat short b = Half.toHalf(blue);
|
||||
|
||||
int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
|
||||
|
||||
// Suppress sign extension
|
||||
return (r & 0xffffL) << 48 |
|
||||
(g & 0xffffL) << 32 |
|
||||
(b & 0xffffL) << 16 |
|
||||
(a & 0x3ffL ) << 6 |
|
||||
id & 0x3fL;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
ColorSpace source = colorSpace(color);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace source, @NonNull ColorSpace destination) {
|
||||
float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, destination);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
return convert(r, g, b, a, connector);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace.Connector connector) {
|
||||
float[] c = connector.transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, connector.getDestination());
|
||||
}
|
||||
|
||||
public static float luminance(@ColorLong long color) {
|
||||
ColorSpace colorSpace = colorSpace(color);
|
||||
if (colorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + colorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(red(color));
|
||||
double g = eotf.applyAsDouble(green(color));
|
||||
double b = eotf.applyAsDouble(blue(color));
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
private static float saturate(float v) {
|
||||
return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int alpha(int color) {
|
||||
return color >>> 24;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int red(int color) {
|
||||
return (color >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int green(int color) {
|
||||
return (color >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int blue(int color) {
|
||||
return color & 0xFF;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(float red, float green, float blue) {
|
||||
return 0xff000000 |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(
|
||||
@IntRange(from = 0, to = 255) int alpha,
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(float alpha, float red, float green, float blue) {
|
||||
return ((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public static float luminance(@ColorInt int color) {
|
||||
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
DoubleUnaryOperator eotf = cs.getEotf();
|
||||
|
||||
double r = eotf.applyAsDouble(red(color) / 255.0);
|
||||
double g = eotf.applyAsDouble(green(color) / 255.0);
|
||||
double b = eotf.applyAsDouble(blue(color) / 255.0);
|
||||
|
||||
return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int parseColor(@Size(min=1) String colorString) {
|
||||
if (colorString.charAt(0) == '#') {
|
||||
// Use a long to avoid rollovers on #ffXXXXXX
|
||||
long color = Long.parseLong(colorString.substring(1), 16);
|
||||
if (colorString.length() == 7) {
|
||||
// Set the alpha value
|
||||
color |= 0x00000000ff000000;
|
||||
} else if (colorString.length() != 9) {
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
return (int)color;
|
||||
} else {
|
||||
Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
|
||||
if (color != null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
|
||||
public static void RGBToHSV(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
nativeRGBToHSV(red, green, blue, hsv);
|
||||
}
|
||||
|
||||
public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) {
|
||||
RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@Size(3) float hsv[]) {
|
||||
return HSVToColor(0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
return nativeHSVToColor(alpha, hsv);
|
||||
}
|
||||
|
||||
private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]);
|
||||
private static native int nativeHSVToColor(int alpha, float hsv[]);
|
||||
|
||||
private static final HashMap<String, Integer> sColorNameMap;
|
||||
static {
|
||||
sColorNameMap = new HashMap<>();
|
||||
sColorNameMap.put("black", BLACK);
|
||||
sColorNameMap.put("darkgray", DKGRAY);
|
||||
sColorNameMap.put("gray", GRAY);
|
||||
sColorNameMap.put("lightgray", LTGRAY);
|
||||
sColorNameMap.put("white", WHITE);
|
||||
sColorNameMap.put("red", RED);
|
||||
sColorNameMap.put("green", GREEN);
|
||||
sColorNameMap.put("blue", BLUE);
|
||||
sColorNameMap.put("yellow", YELLOW);
|
||||
sColorNameMap.put("cyan", CYAN);
|
||||
sColorNameMap.put("magenta", MAGENTA);
|
||||
sColorNameMap.put("aqua", 0xFF00FFFF);
|
||||
sColorNameMap.put("fuchsia", 0xFFFF00FF);
|
||||
sColorNameMap.put("darkgrey", DKGRAY);
|
||||
sColorNameMap.put("grey", GRAY);
|
||||
sColorNameMap.put("lightgrey", LTGRAY);
|
||||
sColorNameMap.put("lime", 0xFF00FF00);
|
||||
sColorNameMap.put("maroon", 0xFF800000);
|
||||
sColorNameMap.put("navy", 0xFF000080);
|
||||
sColorNameMap.put("olive", 0xFF808000);
|
||||
sColorNameMap.put("purple", 0xFF800080);
|
||||
sColorNameMap.put("silver", 0xFFC0C0C0);
|
||||
sColorNameMap.put("teal", 0xFF008080);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1847
AndroidCompat/src/main/java/android/graphics/ColorSpace.java
Normal file
1847
AndroidCompat/src/main/java/android/graphics/ColorSpace.java
Normal file
File diff suppressed because it is too large
Load Diff
1549
AndroidCompat/src/main/java/android/graphics/Paint.java
Normal file
1549
AndroidCompat/src/main/java/android/graphics/Paint.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import android.os.Parcel;
|
||||
|
||||
public final class Rect {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
public int left;
|
||||
public int top;
|
||||
public int right;
|
||||
public int bottom;
|
||||
|
||||
private static final class UnflattenHelper {
|
||||
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package android.graphics;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class TemporaryBuffer {
|
||||
public static char[] obtain(int len) {
|
||||
char[] buf;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
buf = sTemp;
|
||||
sTemp = null;
|
||||
}
|
||||
|
||||
if (buf == null || buf.length < len) {
|
||||
buf = ArrayUtils.newUnpaddedCharArray(len);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static void recycle(char[] temp) {
|
||||
if (temp.length > 1000) return;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
sTemp = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private static char[] sTemp = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user