mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01: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:
@@ -47,4 +47,5 @@ dependencies {
|
|||||||
|
|
||||||
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
||||||
implementation(libs.bundles.twelvemonkeys)
|
implementation(libs.bundles.twelvemonkeys)
|
||||||
|
implementation(libs.sejda.webp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Denotes that the annotated element represents a packed color
|
||||||
|
* long. If applied to a long array, every element in the array
|
||||||
|
* represents a color long. For more information on how colors
|
||||||
|
* are packed in a long, please refer to the documentation of
|
||||||
|
* the {@link android.graphics.Color} class.</p>
|
||||||
|
*
|
||||||
|
* <p>Example:</p>
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public void setFillColor(@ColorLong long color);
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see android.graphics.Color
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
|
||||||
|
public @interface ColorLong {
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Denotes that the annotated element represents a half-precision floating point
|
||||||
|
* value. Such values are stored in short data types and can be manipulated with
|
||||||
|
* the {@link android.util.Half} class. If applied to an array of short, every
|
||||||
|
* element in the array represents a half-precision float.</p>
|
||||||
|
*
|
||||||
|
* <p>Example:</p>
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see android.util.Half
|
||||||
|
* @see android.util.Half#toHalf(float)
|
||||||
|
* @see android.util.Half#toFloat(short)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
|
||||||
|
public @interface HalfFloat {
|
||||||
|
}
|
||||||
@@ -2,17 +2,17 @@ package android.graphics;
|
|||||||
|
|
||||||
import android.annotation.ColorInt;
|
import android.annotation.ColorInt;
|
||||||
import android.annotation.NonNull;
|
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.IIOImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
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 {
|
public final class Bitmap {
|
||||||
private final int width;
|
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
|
* 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) {
|
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);
|
return new Bitmap(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +157,10 @@ public final class Bitmap {
|
|||||||
formatString = "png";
|
formatString = "png";
|
||||||
} else if (format == Bitmap.CompressFormat.JPEG) {
|
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||||
formatString = "jpg";
|
formatString = "jpg";
|
||||||
|
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||||
|
formatString = "webp";
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported compression format!");
|
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||||
@@ -162,14 +177,19 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
writer.setOutput(ios);
|
writer.setOutput(ios);
|
||||||
|
|
||||||
|
BufferedImage img = image;
|
||||||
|
|
||||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
if ("jpg".equals(formatString)) {
|
if ("jpg".equals(formatString)) {
|
||||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
param.setCompressionQuality(qualityFloat);
|
param.setCompressionQuality(qualityFloat);
|
||||||
|
|
||||||
|
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
img.getGraphics().drawImage(image, 0, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writer.write(null, new IIOImage(image, null, null), param);
|
writer.write(null, new IIOImage(img, null, null), param);
|
||||||
ios.close();
|
ios.close();
|
||||||
writer.dispose();
|
writer.dispose();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@@ -179,6 +199,12 @@ public final class Bitmap {
|
|||||||
return true;
|
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()
|
* Shared code to check for illegal arguments passed to getPixels()
|
||||||
* or setPixels()
|
* or setPixels()
|
||||||
@@ -224,12 +250,18 @@ public final class Bitmap {
|
|||||||
int x, int y, int width, int height) {
|
int x, int y, int width, int height) {
|
||||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
|
|
||||||
Raster raster = image.getData();
|
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||||
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
|
}
|
||||||
|
|
||||||
for (int ht = 0; ht < height; ht++) {
|
public void eraseColor(int c) {
|
||||||
int rowOffset = offset + stride * ht;
|
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||||
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
|
Graphics2D graphics = image.createGraphics();
|
||||||
}
|
graphics.setColor(color);
|
||||||
|
graphics.fillRect(0, 0, width, height);
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recycle() {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
package android.graphics;
|
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.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.awt.image.BufferedImage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public final class Canvas {
|
public final class Canvas {
|
||||||
private BufferedImage canvasImage;
|
private BufferedImage canvasImage;
|
||||||
private Graphics2D canvas;
|
private Graphics2D canvas;
|
||||||
|
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||||
|
|
||||||
|
private static final String TAG = "Canvas";
|
||||||
|
|
||||||
public Canvas(Bitmap bitmap) {
|
public Canvas(Bitmap bitmap) {
|
||||||
canvasImage = bitmap.getImage();
|
canvasImage = bitmap.getImage();
|
||||||
@@ -16,6 +30,155 @@ public final class Canvas {
|
|||||||
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 sourceImage = sourceBitmap.getImage();
|
||||||
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
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;
|
package android.graphics;
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
public final class Rect {
|
public final class Rect {
|
||||||
int left;
|
public int left;
|
||||||
int top;
|
public int top;
|
||||||
int right;
|
public int right;
|
||||||
int bottom;
|
public int bottom;
|
||||||
|
|
||||||
private static final class UnflattenHelper {
|
private static final class UnflattenHelper {
|
||||||
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
1905
AndroidCompat/src/main/java/android/text/Layout.java
Normal file
1905
AndroidCompat/src/main/java/android/text/Layout.java
Normal file
File diff suppressed because it is too large
Load Diff
631
AndroidCompat/src/main/java/android/text/StaticLayout.java
Normal file
631
AndroidCompat/src/main/java/android/text/StaticLayout.java
Normal file
@@ -0,0 +1,631 @@
|
|||||||
|
/*
|
||||||
|
* 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.text;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.IntRange;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.font.LineBreakMeasurer;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.font.TextLayout;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
import com.android.internal.util.GrowingArrayUtils;
|
||||||
|
|
||||||
|
|
||||||
|
public class StaticLayout extends Layout {
|
||||||
|
/*
|
||||||
|
* The break iteration is done in native code. The protocol for using the native code is as
|
||||||
|
* follows.
|
||||||
|
*
|
||||||
|
* First, call nInit to setup native line breaker object. Then, for each paragraph, do the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
|
||||||
|
* native.
|
||||||
|
* - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
|
||||||
|
*
|
||||||
|
* After all paragraphs, call finish() to release expensive buffers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static final String TAG = "StaticLayout";
|
||||||
|
|
||||||
|
public final static class Builder {
|
||||||
|
private Builder() {}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
|
||||||
|
@IntRange(from = 0) int end, @NonNull TextPaint paint,
|
||||||
|
@IntRange(from = 0) int width) {
|
||||||
|
Builder b = new Builder();
|
||||||
|
|
||||||
|
// set default initial values
|
||||||
|
b.mText = source;
|
||||||
|
b.mStart = start;
|
||||||
|
b.mEnd = end;
|
||||||
|
b.mPaint = paint;
|
||||||
|
b.mWidth = width;
|
||||||
|
b.mAlignment = Alignment.ALIGN_NORMAL;
|
||||||
|
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||||
|
b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
|
||||||
|
b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
|
||||||
|
b.mIncludePad = true;
|
||||||
|
b.mFallbackLineSpacing = false;
|
||||||
|
b.mEllipsizedWidth = width;
|
||||||
|
b.mEllipsize = null;
|
||||||
|
b.mMaxLines = Integer.MAX_VALUE;
|
||||||
|
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||||
|
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||||
|
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
|
||||||
|
b.mMinimumFontMetrics = null;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// release any expensive state
|
||||||
|
/* package */ void finish() {
|
||||||
|
mText = null;
|
||||||
|
mPaint = null;
|
||||||
|
mLeftIndents = null;
|
||||||
|
mRightIndents = null;
|
||||||
|
mMinimumFontMetrics = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setText(CharSequence source) {
|
||||||
|
return setText(source, 0, source.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setText(@NonNull CharSequence source, int start, int end) {
|
||||||
|
mText = source;
|
||||||
|
mStart = start;
|
||||||
|
mEnd = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setPaint(@NonNull TextPaint paint) {
|
||||||
|
mPaint = paint;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setWidth(@IntRange(from = 0) int width) {
|
||||||
|
mWidth = width;
|
||||||
|
if (mEllipsize == null) {
|
||||||
|
mEllipsizedWidth = width;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setAlignment(@NonNull Alignment alignment) {
|
||||||
|
mAlignment = alignment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||||
|
mTextDir = textDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setLineSpacing(float spacingAdd, float spacingMult) {
|
||||||
|
mSpacingAdd = spacingAdd;
|
||||||
|
mSpacingMult = spacingMult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setIncludePad(boolean includePad) {
|
||||||
|
mIncludePad = includePad;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
|
||||||
|
mFallbackLineSpacing = useLineSpacingFromFallbacks;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
|
||||||
|
mEllipsizedWidth = ellipsizedWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
|
||||||
|
mEllipsize = ellipsize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
|
||||||
|
mMaxLines = maxLines;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
|
||||||
|
mBreakStrategy = breakStrategy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
|
||||||
|
mHyphenationFrequency = hyphenationFrequency;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
|
||||||
|
mLeftIndents = leftIndents;
|
||||||
|
mRightIndents = rightIndents;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setJustificationMode(@JustificationMode int justificationMode) {
|
||||||
|
mJustificationMode = justificationMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
/* package */ Builder setAddLastLineLineSpacing(boolean value) {
|
||||||
|
mAddLastLineLineSpacing = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
|
||||||
|
@NonNull
|
||||||
|
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
|
||||||
|
mUseBoundsForWidth = useBoundsForWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
// The corresponding getter is getShiftDrawingOffsetForStartOverhang()
|
||||||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||||||
|
public Builder setShiftDrawingOffsetForStartOverhang(
|
||||||
|
boolean shiftDrawingOffsetForStartOverhang) {
|
||||||
|
mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCalculateBounds(boolean value) {
|
||||||
|
mCalculateBounds = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
|
||||||
|
mMinimumFontMetrics = minimumFontMetrics;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public StaticLayout build() {
|
||||||
|
StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
|
||||||
|
? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence mText;
|
||||||
|
private int mStart;
|
||||||
|
private int mEnd;
|
||||||
|
private TextPaint mPaint;
|
||||||
|
private int mWidth;
|
||||||
|
private Alignment mAlignment;
|
||||||
|
private TextDirectionHeuristic mTextDir;
|
||||||
|
private float mSpacingMult;
|
||||||
|
private float mSpacingAdd;
|
||||||
|
private boolean mIncludePad;
|
||||||
|
private boolean mFallbackLineSpacing;
|
||||||
|
private int mEllipsizedWidth;
|
||||||
|
private TextUtils.TruncateAt mEllipsize;
|
||||||
|
private int mMaxLines;
|
||||||
|
private int mBreakStrategy;
|
||||||
|
private int mHyphenationFrequency;
|
||||||
|
@Nullable private int[] mLeftIndents;
|
||||||
|
@Nullable private int[] mRightIndents;
|
||||||
|
private int mJustificationMode;
|
||||||
|
private boolean mAddLastLineLineSpacing;
|
||||||
|
private boolean mUseBoundsForWidth;
|
||||||
|
private boolean mShiftDrawingOffsetForStartOverhang;
|
||||||
|
private boolean mCalculateBounds;
|
||||||
|
@Nullable private Paint.FontMetrics mMinimumFontMetrics;
|
||||||
|
|
||||||
|
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticLayout() {
|
||||||
|
super(
|
||||||
|
null, // text
|
||||||
|
null, // paint
|
||||||
|
0, // width
|
||||||
|
null, // alignment
|
||||||
|
null, // textDir
|
||||||
|
1, // spacing multiplier
|
||||||
|
0, // spacing amount
|
||||||
|
false, // include font padding
|
||||||
|
false, // fallback line spacing
|
||||||
|
0, // ellipsized width
|
||||||
|
null, // ellipsize
|
||||||
|
1, // maxLines
|
||||||
|
BREAK_STRATEGY_SIMPLE,
|
||||||
|
HYPHENATION_FREQUENCY_NONE,
|
||||||
|
null, // leftIndents
|
||||||
|
null, // rightIndents
|
||||||
|
JUSTIFICATION_MODE_NONE,
|
||||||
|
false, // useBoundsForWidth
|
||||||
|
false, // shiftDrawingOffsetForStartOverhang
|
||||||
|
null // minimumFontMetrics
|
||||||
|
);
|
||||||
|
|
||||||
|
mColumns = COLUMNS_ELLIPSIZE;
|
||||||
|
mLineDirections = new Directions[2];
|
||||||
|
mLines = new int[2 * mColumns];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, TextPaint paint,
|
||||||
|
int width,
|
||||||
|
Alignment align, float spacingmult, float spacingadd,
|
||||||
|
boolean includepad) {
|
||||||
|
this(source, 0, source.length(), paint, width, align,
|
||||||
|
spacingmult, spacingadd, includepad);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad) {
|
||||||
|
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||||
|
spacingmult, spacingadd, includepad, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad,
|
||||||
|
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
|
||||||
|
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||||
|
TextDirectionHeuristics.FIRSTSTRONG_LTR,
|
||||||
|
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align, TextDirectionHeuristic textDir,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad,
|
||||||
|
TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
|
||||||
|
this(Builder.obtain(source, bufstart, bufend, paint, outerwidth)
|
||||||
|
.setAlignment(align)
|
||||||
|
.setTextDirection(textDir)
|
||||||
|
.setLineSpacing(spacingadd, spacingmult)
|
||||||
|
.setIncludePad(includepad)
|
||||||
|
.setEllipsize(ellipsize)
|
||||||
|
.setEllipsizedWidth(ellipsizedWidth)
|
||||||
|
.setMaxLines(maxLines), includepad,
|
||||||
|
ellipsize != null ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticLayout(Builder b, boolean trackPadding, int columnSize) {
|
||||||
|
super((b.mEllipsize == null) ? b.mText : (b.mText instanceof Spanned)
|
||||||
|
? new SpannedEllipsizer(b.mText) : new Ellipsizer(b.mText),
|
||||||
|
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
|
||||||
|
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
|
||||||
|
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
|
||||||
|
b.mRightIndents, b.mJustificationMode, b.mUseBoundsForWidth,
|
||||||
|
b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics);
|
||||||
|
|
||||||
|
mColumns = columnSize;
|
||||||
|
if (b.mEllipsize != null) {
|
||||||
|
Ellipsizer e = (Ellipsizer) getText();
|
||||||
|
|
||||||
|
e.mLayout = this;
|
||||||
|
e.mWidth = b.mEllipsizedWidth;
|
||||||
|
e.mMethod = b.mEllipsize;
|
||||||
|
throw new UnsupportedOperationException("Ellipsis not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
mLineDirections = new Directions[2];
|
||||||
|
mLines = new int[2 * mColumns];
|
||||||
|
mMaximumVisibleLineCount = b.mMaxLines;
|
||||||
|
|
||||||
|
mLeftIndents = b.mLeftIndents;
|
||||||
|
mRightIndents = b.mRightIndents;
|
||||||
|
|
||||||
|
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||||
|
AttributedString text = new AttributedString(str);
|
||||||
|
text.addAttribute(TextAttribute.FONT, getPaint().getFont());
|
||||||
|
FontRenderContext frc = new FontRenderContext(getPaint().getFont().getTransform(), RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
|
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||||
|
// TODO: directions
|
||||||
|
|
||||||
|
float y = 0;
|
||||||
|
while (measurer.getPosition() < str.length()) {
|
||||||
|
int off = mLineCount * mColumns;
|
||||||
|
int want = off + mColumns + TOP;
|
||||||
|
if (want >= mLines.length) {
|
||||||
|
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
|
||||||
|
System.arraycopy(mLines, 0, grow, 0, mLines.length);
|
||||||
|
mLines = grow;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = measurer.getPosition();
|
||||||
|
TextLayout l = measurer.nextLayout(getWidth());
|
||||||
|
mLines[off + START] = pos;
|
||||||
|
mLines[off + TOP] = (int) y;
|
||||||
|
mLines[off + DESCENT] = (int) (l.getDescent() + l.getLeading());
|
||||||
|
mLines[off + EXTRA] = (int) l.getLeading();
|
||||||
|
mLines[off + DIR] |= Layout.DIR_LEFT_TO_RIGHT << DIR_SHIFT;
|
||||||
|
|
||||||
|
y += l.getAscent();
|
||||||
|
y += l.getDescent() + l.getLeading();
|
||||||
|
|
||||||
|
mLines[off + mColumns + START] = measurer.getPosition();
|
||||||
|
mLines[off + mColumns + TOP] = (int) y;
|
||||||
|
|
||||||
|
mLineCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override the base class so we can directly access our members,
|
||||||
|
// rather than relying on member functions.
|
||||||
|
// The logic mirrors that of Layout.getLineForVertical
|
||||||
|
// FIXME: It may be faster to do a linear search for layouts without many lines.
|
||||||
|
@Override
|
||||||
|
public int getLineForVertical(int vertical) {
|
||||||
|
int high = mLineCount;
|
||||||
|
int low = -1;
|
||||||
|
int guess;
|
||||||
|
int[] lines = mLines;
|
||||||
|
while (high - low > 1) {
|
||||||
|
guess = (high + low) >> 1;
|
||||||
|
if (lines[mColumns * guess + TOP] > vertical){
|
||||||
|
high = guess;
|
||||||
|
} else {
|
||||||
|
low = guess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (low < 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineCount() {
|
||||||
|
return mLineCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineTop(int line) {
|
||||||
|
return mLines[mColumns * line + TOP];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineExtra(int line) {
|
||||||
|
return mLines[mColumns * line + EXTRA];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineDescent(int line) {
|
||||||
|
return mLines[mColumns * line + DESCENT];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineStart(int line) {
|
||||||
|
return mLines[mColumns * line + START] & START_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getParagraphDirection(int line) {
|
||||||
|
return mLines[mColumns * line + DIR] >> DIR_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getLineContainsTab(int line) {
|
||||||
|
return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Directions getLineDirections(int line) {
|
||||||
|
if (line > getLineCount()) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
return new Directions(null);
|
||||||
|
// return mLineDirections[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTopPadding() {
|
||||||
|
return mTopPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBottomPadding() {
|
||||||
|
return mBottomPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To store into single int field, pack the pair of start and end hyphen edit.
|
||||||
|
static int packHyphenEdit(
|
||||||
|
@Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
|
||||||
|
return start << START_HYPHEN_BITS_SHIFT | end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpackStartHyphenEdit(int packedHyphenEdit) {
|
||||||
|
return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpackEndHyphenEdit(int packedHyphenEdit) {
|
||||||
|
return packedHyphenEdit & END_HYPHEN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
|
||||||
|
return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
|
||||||
|
return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndentAdjust(int line, Alignment align) {
|
||||||
|
if (align == Alignment.ALIGN_LEFT) {
|
||||||
|
if (mLeftIndents == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||||
|
}
|
||||||
|
} else if (align == Alignment.ALIGN_RIGHT) {
|
||||||
|
if (mRightIndents == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||||
|
}
|
||||||
|
} else if (align == Alignment.ALIGN_CENTER) {
|
||||||
|
int left = 0;
|
||||||
|
if (mLeftIndents != null) {
|
||||||
|
left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||||
|
}
|
||||||
|
int right = 0;
|
||||||
|
if (mRightIndents != null) {
|
||||||
|
right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||||
|
}
|
||||||
|
return (left - right) >> 1;
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("unhandled alignment " + align);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEllipsisCount(int line) {
|
||||||
|
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLines[mColumns * line + ELLIPSIS_COUNT];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEllipsisStart(int line) {
|
||||||
|
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLines[mColumns * line + ELLIPSIS_START];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public RectF computeDrawingBoundingBox() {
|
||||||
|
// Cache the drawing bounds result because it does not change after created.
|
||||||
|
if (mDrawingBounds == null) {
|
||||||
|
mDrawingBounds = super.computeDrawingBoundingBox();
|
||||||
|
}
|
||||||
|
return mDrawingBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight(boolean cap) {
|
||||||
|
if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
|
||||||
|
&& Log.isLoggable(TAG, Log.WARN)) {
|
||||||
|
Log.w(TAG, "maxLineHeight should not be -1. "
|
||||||
|
+ " maxLines:" + mMaximumVisibleLineCount
|
||||||
|
+ " lineCount:" + mLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
|
||||||
|
? mMaxLineHeight : super.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mLineCount;
|
||||||
|
private int mTopPadding, mBottomPadding;
|
||||||
|
private int mColumns;
|
||||||
|
private RectF mDrawingBounds = null; // lazy calculation.
|
||||||
|
|
||||||
|
private boolean mEllipsized;
|
||||||
|
|
||||||
|
private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
|
||||||
|
|
||||||
|
private static final int COLUMNS_NORMAL = 5;
|
||||||
|
private static final int COLUMNS_ELLIPSIZE = 7;
|
||||||
|
private static final int START = 0;
|
||||||
|
private static final int DIR = START;
|
||||||
|
private static final int TAB = START;
|
||||||
|
private static final int TOP = 1;
|
||||||
|
private static final int DESCENT = 2;
|
||||||
|
private static final int EXTRA = 3;
|
||||||
|
private static final int HYPHEN = 4;
|
||||||
|
private static final int ELLIPSIS_START = 5;
|
||||||
|
private static final int ELLIPSIS_COUNT = 6;
|
||||||
|
|
||||||
|
private int[] mLines;
|
||||||
|
private Directions[] mLineDirections;
|
||||||
|
private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private static final int START_MASK = 0x1FFFFFFF;
|
||||||
|
private static final int DIR_SHIFT = 30;
|
||||||
|
private static final int TAB_MASK = 0x20000000;
|
||||||
|
private static final int HYPHEN_MASK = 0xFF;
|
||||||
|
private static final int START_HYPHEN_BITS_SHIFT = 3;
|
||||||
|
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
|
||||||
|
private static final int END_HYPHEN_MASK = 0x7; // 0b00111
|
||||||
|
|
||||||
|
private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
|
||||||
|
|
||||||
|
private static final char CHAR_NEW_LINE = '\n';
|
||||||
|
|
||||||
|
private static final double EXTRA_ROUNDING = 0.5;
|
||||||
|
|
||||||
|
private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
|
||||||
|
|
||||||
|
// Unused, here because of gray list private API accesses.
|
||||||
|
/*package*/ static class LineBreaks {
|
||||||
|
private static final int INITIAL_SIZE = 16;
|
||||||
|
public int[] breaks = new int[INITIAL_SIZE];
|
||||||
|
public float[] widths = new float[INITIAL_SIZE];
|
||||||
|
public float[] ascents = new float[INITIAL_SIZE];
|
||||||
|
public float[] descents = new float[INITIAL_SIZE];
|
||||||
|
public int[] flags = new int[INITIAL_SIZE]; // hasTab
|
||||||
|
// breaks, widths, and flags should all have the same length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable private int[] mLeftIndents;
|
||||||
|
@Nullable private int[] mRightIndents;
|
||||||
|
}
|
||||||
|
|
||||||
178
AndroidCompat/src/main/java/android/text/TextLine.java
Normal file
178
AndroidCompat/src/main/java/android/text/TextLine.java
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 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.text;
|
||||||
|
|
||||||
|
import android.annotation.IntRange;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint.FontMetricsInt;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.text.Layout.Directions;
|
||||||
|
import android.text.Layout.TabStops;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
|
||||||
|
|
||||||
|
public class TextLine {
|
||||||
|
private TextPaint mPaint;
|
||||||
|
private CharSequence mText;
|
||||||
|
private int mStart;
|
||||||
|
private int mLen;
|
||||||
|
private int mDir;
|
||||||
|
private Directions mDirections;
|
||||||
|
private boolean mHasTabs;
|
||||||
|
private TabStops mTabs;
|
||||||
|
private char[] mChars;
|
||||||
|
private boolean mCharsValid;
|
||||||
|
private Spanned mSpanned;
|
||||||
|
private PrecomputedText mComputed;
|
||||||
|
private RectF mTmpRectForMeasure;
|
||||||
|
private RectF mTmpRectForPaintAPI;
|
||||||
|
private Rect mTmpRectForPrecompute;
|
||||||
|
|
||||||
|
|
||||||
|
public static final class LineInfo {
|
||||||
|
private int mClusterCount;
|
||||||
|
|
||||||
|
public int getClusterCount() {
|
||||||
|
return mClusterCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClusterCount(int clusterCount) {
|
||||||
|
mClusterCount = clusterCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public float getAddedWordSpacingInPx() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAddedLetterSpacingInPx() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isJustifying() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Not allowed to access. If it's for memory leak workaround, it was already fixed M. */
|
||||||
|
private static final TextLine[] sCached = new TextLine[3];
|
||||||
|
|
||||||
|
public static TextLine obtain() {
|
||||||
|
TextLine tl;
|
||||||
|
synchronized (sCached) {
|
||||||
|
for (int i = sCached.length; --i >= 0;) {
|
||||||
|
if (sCached[i] != null) {
|
||||||
|
tl = sCached[i];
|
||||||
|
sCached[i] = null;
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tl = new TextLine();
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextLine recycle(TextLine tl) {
|
||||||
|
synchronized(sCached) {
|
||||||
|
for (int i = 0; i < sCached.length; ++i) {
|
||||||
|
if (sCached[i] == null) {
|
||||||
|
sCached[i] = tl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
|
||||||
|
Directions directions, boolean hasTabs, TabStops tabStops,
|
||||||
|
int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) {
|
||||||
|
mPaint = paint;
|
||||||
|
mText = text;
|
||||||
|
mStart = start;
|
||||||
|
mLen = limit - start;
|
||||||
|
mDir = dir;
|
||||||
|
mDirections = directions;
|
||||||
|
if (mDirections == null) {
|
||||||
|
throw new IllegalArgumentException("Directions cannot be null");
|
||||||
|
}
|
||||||
|
mHasTabs = hasTabs;
|
||||||
|
mSpanned = null;
|
||||||
|
|
||||||
|
if (text instanceof Spanned) {
|
||||||
|
mSpanned = (Spanned) text;
|
||||||
|
}
|
||||||
|
|
||||||
|
mComputed = null;
|
||||||
|
if (text instanceof PrecomputedText) {
|
||||||
|
// Here, no need to check line break strategy or hyphenation frequency since there is no
|
||||||
|
// line break concept here.
|
||||||
|
mComputed = (PrecomputedText) text;
|
||||||
|
if (!mComputed.getParams().getTextPaint().equalsForTextMeasurement(paint)) {
|
||||||
|
mComputed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mTabs = tabStops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
|
||||||
|
int runEnd, int spanStart, int spanEnd) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||||
|
@Nullable LineInfo lineInfo) {
|
||||||
|
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
|
return (float) mPaint.getFont().getStringBounds(mText.toString(), mStart, mStart + mLen, frc).getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||||
|
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
|
||||||
|
public static boolean isLineEndSpace(char ch) {
|
||||||
|
return ch == ' ' || ch == '\t' || ch == 0x1680
|
||||||
|
|| (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
|
||||||
|
|| ch == 0x205F || ch == 0x3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(Canvas c, float x, int top, int y, int bottom) {
|
||||||
|
c.drawText(mText, mStart, mStart + mLen, x, y, mPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
AndroidCompat/src/main/java/android/text/TextPaint.java
Normal file
74
AndroidCompat/src/main/java/android/text/TextPaint.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.text;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
public class TextPaint extends Paint {
|
||||||
|
|
||||||
|
// Special value 0 means no background paint
|
||||||
|
@ColorInt
|
||||||
|
public int bgColor;
|
||||||
|
public int baselineShift;
|
||||||
|
@ColorInt
|
||||||
|
public int linkColor;
|
||||||
|
public int[] drawableState;
|
||||||
|
public float density = 1.0f;
|
||||||
|
@ColorInt
|
||||||
|
public int underlineColor = 0;
|
||||||
|
|
||||||
|
public float underlineThickness;
|
||||||
|
|
||||||
|
public TextPaint() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextPaint(int flags) {
|
||||||
|
super(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextPaint(Paint p) {
|
||||||
|
super(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(TextPaint tp) {
|
||||||
|
super.set(tp);
|
||||||
|
|
||||||
|
bgColor = tp.bgColor;
|
||||||
|
baselineShift = tp.baselineShift;
|
||||||
|
linkColor = tp.linkColor;
|
||||||
|
drawableState = tp.drawableState;
|
||||||
|
density = tp.density;
|
||||||
|
underlineColor = tp.underlineColor;
|
||||||
|
underlineThickness = tp.underlineThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnderlineText(int color, float thickness) {
|
||||||
|
underlineColor = color;
|
||||||
|
underlineThickness = thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getUnderlineThickness() {
|
||||||
|
if (underlineColor != 0) { // Return custom thickness only if underline color is set.
|
||||||
|
return underlineThickness;
|
||||||
|
} else {
|
||||||
|
return super.getUnderlineThickness();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import libcore.util.EmptyArray;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import libcore.util.EmptyArray;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ArrayUtils contains some methods that you can call to find out
|
* ArrayUtils contains some methods that you can call to find out
|
||||||
* the most efficient increments by which to grow arrays.
|
* the most efficient increments by which to grow arrays.
|
||||||
@@ -50,6 +50,10 @@ public class ArrayUtils {
|
|||||||
public static Object[] newUnpaddedObjectArray(int minLen) {
|
public static Object[] newUnpaddedObjectArray(int minLen) {
|
||||||
return new Object[minLen];
|
return new Object[minLen];
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
|
||||||
|
return (T[])Array.newInstance(clazz, minLen);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Checks if the beginnings of two byte arrays are equal.
|
* Checks if the beginnings of two byte arrays are equal.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 com.android.internal.util;
|
||||||
|
|
||||||
|
public final class GrowingArrayUtils {
|
||||||
|
|
||||||
|
public static <T> T[] append(T[] array, int currentSize, T element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] newArray = ArrayUtils.newUnpaddedArray(
|
||||||
|
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] append(int[] array, int currentSize, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] append(long[] array, int currentSize, long element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean[] append(boolean[] array, int currentSize, boolean element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] append(float[] array, int currentSize, float element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
|
||||||
|
growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] insert(int[] array, int currentSize, int index, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] insert(long[] array, int currentSize, int index, long element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int growSize(int currentSize) {
|
||||||
|
return currentSize <= 4 ? 8 : currentSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstantiable
|
||||||
|
private GrowingArrayUtils() {}
|
||||||
|
}
|
||||||
@@ -136,6 +136,8 @@ twelvemonkeys-imageio-metadata = { module = "com.twelvemonkeys.imageio:imageio-m
|
|||||||
twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg", version.ref = "twelvemonkeys" }
|
twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg", version.ref = "twelvemonkeys" }
|
||||||
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
|
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
|
||||||
|
|
||||||
|
sejda-webp = "com.github.usefulness:webp-imageio:0.10.0"
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
mockk = "io.mockk:mockk:1.14.2"
|
mockk = "io.mockk:mockk:1.14.2"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user