[androidkit] Image shader support

Tangential updates:

 - new encoded input stream Image factory
 - Paint setters now return the object to support a fluent workflow
 - cube demo updated to store face paints instead of colors


Change-Id: I6142a229b18165112ef1fe76acae38bc4b27480d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/410789
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Jorge Betancourt <jmbetancourt@google.com>
This commit is contained in:
Florin Malita 2021-05-21 08:23:32 -04:00 committed by Skia Commit-Bot
parent 8017581ec0
commit 50cc5d4147
10 changed files with 183 additions and 46 deletions

View File

@ -2505,6 +2505,7 @@ if (skia_enable_tools) {
"modules/androidkit/src/Surface.h",
"modules/androidkit/src/SurfaceThread.cpp",
"modules/androidkit/src/SurfaceThread.h",
"modules/androidkit/src/Utils.cpp",
]
libs = [
"android",

View File

@ -8,24 +8,12 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "modules/androidkit/src/Utils.h"
#include <jni.h>
namespace {
SkSamplingOptions sampling_opts(jint desc, jfloat coeffB, jfloat coeffC) {
if (desc & 0x01) {
return SkSamplingOptions(SkCubicResampler{coeffB, coeffC});
}
const auto fm = static_cast<SkFilterMode>((desc >> 1) & 0x01);
SkASSERT(fm <= SkFilterMode::kLast);
const auto mm = static_cast<SkMipmapMode>((desc >> 2) & 0x03);
SkASSERT(mm <= SkMipmapMode::kLast);
return SkSamplingOptions(fm, mm);
}
jint Canvas_GetWidth(JNIEnv* env, jobject, jlong native_instance) {
const auto* canvas = reinterpret_cast<const SkCanvas*>(native_instance);
return canvas ? canvas->imageInfo().width() : 0;
@ -99,7 +87,8 @@ void Canvas_DrawImage(JNIEnv* env, jobject, jlong native_instance, jlong native_
auto* image = reinterpret_cast<SkImage *>(native_image);
if (canvas && image) {
canvas->drawImage(image, x, y, sampling_opts(sampling_desc, sampling_b, sampling_c));
canvas->drawImage(image, x, y,
androidkit::utils::SamplingOptions(sampling_desc, sampling_b, sampling_c));
}
}

View File

@ -8,6 +8,8 @@
#include <jni.h>
#include "include/core/SkImage.h"
#include "include/core/SkTileMode.h"
#include "modules/androidkit/src/Utils.h"
namespace {
@ -35,15 +37,37 @@ jint Image_GetHeight(JNIEnv*, jobject, jlong native_instance) {
return image ? image->height() : 0;
}
jlong Image_MakeShader(JNIEnv*, jobject, jlong native_instance, jint jtmx, jint jtmy,
jint sampling_desc, jfloat sampling_b, jfloat sampling_c,
jlong native_matrix) {
sk_sp<SkShader> shader;
if (const auto* image = reinterpret_cast<const SkImage*>(native_instance)) {
const auto tmx = androidkit::utils::TileMode(jtmx),
tmy = androidkit::utils::TileMode(jtmy);
const auto sampling = androidkit::utils::SamplingOptions(sampling_desc,
sampling_b, sampling_c);
const auto* lm = reinterpret_cast<const SkM44*>(native_matrix);
shader = lm
? image->makeShader(tmx, tmy, sampling, lm->asM33())
: image->makeShader(tmx, tmy, sampling);
}
return reinterpret_cast<jlong>(shader.release());
}
} // namespace
int register_androidkit_Image(JNIEnv* env) {
static const JNINativeMethod methods[] = {
{"nCreate" , "([B)J", reinterpret_cast<void*>(Image_Create) },
{"nRelease" , "(J)V" , reinterpret_cast<void*>(Image_Release) },
{"nCreate" , "([B)J" , reinterpret_cast<void*>(Image_Create) },
{"nRelease" , "(J)V" , reinterpret_cast<void*>(Image_Release) },
{"nGetWidth" , "(J)I" , reinterpret_cast<void*>(Image_GetWidth) },
{"nGetHeight", "(J)I" , reinterpret_cast<void*>(Image_GetHeight)},
{"nGetWidth" , "(J)I" , reinterpret_cast<void*>(Image_GetWidth) },
{"nGetHeight" , "(J)I" , reinterpret_cast<void*>(Image_GetHeight)},
{"nMakeShader", "(JIIIFFJ)J" , reinterpret_cast<void*>(Image_MakeShader)},
};
const auto clazz = env->FindClass("org/skia/androidkit/Image");

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/androidkit/src/Utils.h"
namespace androidkit {
namespace utils {
SkSamplingOptions SamplingOptions(jint desc, jfloat coeffB, jfloat coeffC) {
if (desc & 0x01) {
return SkSamplingOptions(SkCubicResampler{coeffB, coeffC});
}
const auto fm = static_cast<SkFilterMode>((desc >> 1) & 0x01);
SkASSERT(fm <= SkFilterMode::kLast);
const auto mm = static_cast<SkMipmapMode>((desc >> 2) & 0x03);
SkASSERT(mm <= SkMipmapMode::kLast);
return SkSamplingOptions(fm, mm);
}
SkTileMode TileMode(jint tm) {
// to catch Skia API changes
static_assert(static_cast<int>(SkTileMode::kClamp ) == 0);
static_assert(static_cast<int>(SkTileMode::kRepeat) == 1);
static_assert(static_cast<int>(SkTileMode::kMirror) == 2);
static_assert(static_cast<int>(SkTileMode::kDecal ) == 3);
return static_cast<SkTileMode>(tm);
}
} // namespace utils
} // namespace androidkit

View File

@ -7,6 +7,9 @@
#include <jni.h>
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkTileMode.h"
namespace androidkit {
namespace utils {
@ -37,5 +40,8 @@ private:
CString& operator=(const CString&) = delete;
};
SkSamplingOptions SamplingOptions(jint, jfloat, jfloat);
SkTileMode TileMode(jint);
} // namespace utils
} // namespace androidkit

View File

@ -7,6 +7,13 @@
package org.skia.androidkit;
import android.support.annotation.Nullable;
import java.io.InputStream;
import org.skia.androidkit.Matrix;
import org.skia.androidkit.SamplingOptions;
import org.skia.androidkit.Shader;
import org.skia.androidkit.TileMode;
public class Image {
private long mNativeInstance;
@ -22,6 +29,18 @@ public class Image {
: null;
}
/**
* Construct an Image from an encoded data stream.
*
* Returns null for unsupported formats or invalid stream.
*/
public static Image fromStream(InputStream encodedStream) throws java.io.IOException {
byte[] encodedData = new byte[encodedStream.available()];
encodedStream.read(encodedData);
return fromEncoded(encodedData);
}
public int getWidth() {
return nGetWidth(mNativeInstance);
}
@ -30,6 +49,19 @@ public class Image {
return nGetHeight(mNativeInstance);
}
public Shader makeShader(TileMode tmx, TileMode tmy, SamplingOptions sampling) {
return makeShader(tmx, tmy, sampling, null);
}
public Shader makeShader(TileMode tmx, TileMode tmy, SamplingOptions sampling,
@Nullable Matrix localMatrix) {
long nativeMatrix = localMatrix != null ? localMatrix.getNativeInstance() : 0;
return new Shader(nMakeShader(mNativeInstance, tmx.ordinal(), tmy.ordinal(),
sampling.getNativeDesc(),
sampling.getCubicCoeffB(), sampling.getCubicCoeffC(),
nativeMatrix));
}
/**
* Releases any resources associated with this Paint.
*/
@ -56,4 +88,8 @@ public class Image {
private static native int nGetWidth(long nativeInstance);
private static native int nGetHeight(long nativeInstance);
private static native long nMakeShader(long nativeInstance, int tmx, int tmy, int samplingDesc,
float samplingCoeffB, float samplingCoeffC,
long nativeMatrix);
}

View File

@ -17,20 +17,24 @@ public class Paint {
mNativeInstance = nCreate();
}
public void setColor(Color c) {
public Paint setColor(Color c) {
nSetColor(mNativeInstance, c.r(), c.g(), c.b(), c.a());
return this;
}
public void setShader(Shader shader) {
public Paint setShader(Shader shader) {
nSetShader(mNativeInstance, shader != null ? shader.getNativeInstance() : 0);
return this;
}
public void setStroke(boolean stroke) {
public Paint setStroke(boolean stroke) {
nSetStroke(mNativeInstance, stroke);
return this;
}
public void setStrokeWidth(float w) {
public Paint setStrokeWidth(float w) {
nSetStrokeWidth(mNativeInstance, w);
return this;
}
/**
* Releases any resources associated with this Paint.

View File

@ -0,0 +1,33 @@
/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package org.skia.androidkit;
public enum TileMode {
/**
* Replicate the edge color if the shader draws outside of its
* original bounds.
*/
CLAMP,
/**
* Repeat the shader's image horizontally and vertically.
*/
REPEAT,
/**
* Repeat the shader's image horizontally and vertically, alternating
* mirror images so that adjacent images always seam.
*/
MIRROR,
/**
* Only draw within the original domain, return transparent-black everywhere else.
*/
DECAL,
}

View File

@ -8,6 +8,7 @@
package org.skia.androidkitdemo1;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.SurfaceHolder;
@ -15,9 +16,13 @@ import android.view.SurfaceView;
import org.skia.androidkit.Canvas;
import org.skia.androidkit.Color;
import org.skia.androidkit.Image;
import org.skia.androidkit.Matrix;
import org.skia.androidkit.Paint;
import org.skia.androidkit.SamplingOptions;
import org.skia.androidkit.Shader;
import org.skia.androidkit.Surface;
import org.skia.androidkit.TileMode;
import org.skia.androidkit.util.SurfaceRenderer;
import static java.lang.Math.tan;
@ -25,12 +30,12 @@ import static java.lang.Math.tan;
class Face {
private float rotX;
private float rotY;
public Color color;
public Paint paint;
Face(float rotX, float rotY, Color color) {
Face(float rotX, float rotY, Paint paint) {
this.rotX = rotX;
this.rotY = rotY;
this.color = color;
this.paint = paint;
}
Matrix asMatrix(float scale) {
@ -51,20 +56,28 @@ class CubeRenderer extends SurfaceRenderer {
private Matrix perspective = Matrix.makePerspective(0.05f, 4, fAngle);
private Matrix viewport;
private Paint mPaint = new Paint();
private final float rot = (float) Math.PI;
private Face[] faces = {new Face(0, 0, new Color(1, 0, 0, 1)),
new Face(0, rot, new Color(0, 1, 0, 1)),
new Face(rot/2, 0, new Color(0, 0, 1, 1)),
new Face(-rot/2, 0, new Color(1, 1, 0, 1)),
new Face(0, rot/2, new Color(0, 1, 1, 1)),
new Face(0, -rot/2, new Color(0, 0, 0, 1))};
private Face[] faces;
public CubeRenderer() {
mPaint.setColor(new Color(0, 1, 1, 1));
mPaint.setStroke(false);
mPaint.setStrokeWidth(10);
public CubeRenderer(Resources res) {
Paint brickPaint = new Paint();
try {
Image image = Image.fromStream(res.openRawResource(R.raw.brickwork_texture));
Shader shader =
image.makeShader(TileMode.REPEAT, TileMode.REPEAT,
new SamplingOptions(SamplingOptions.FilterMode.LINEAR));
brickPaint.setShader(shader);
} catch (Exception e) {}
faces = new Face[] {
new Face(0, -rot/2, brickPaint),
new Face(0, 0 , new Paint().setColor(new Color(1, 0, 0, 1))),
new Face(0, rot , new Paint().setColor(new Color(0, 1, 0, 1))),
new Face(rot/2, 0 , new Paint().setColor(new Color(0, 0, 1, 1))),
new Face(-rot/2, 0, new Paint().setColor(new Color(1, 1, 0, 1))),
new Face(0, rot/2 , new Paint().setColor(new Color(0, 1, 1, 1))),
};
}
@Override
@ -100,8 +113,7 @@ class CubeRenderer extends SurfaceRenderer {
canvas.concat(localToWorld);
if (front(canvas.getLocalToDevice())) {
mPaint.setColor(f.color);
canvas.drawRect(0, 0, mCubeSideLength, mCubeSideLength, mPaint);
canvas.drawRect(0, 0, mCubeSideLength, mCubeSideLength, f.paint);
}
canvas.restore();
}
@ -130,6 +142,6 @@ public class CubeActivity extends Activity {
setContentView(R.layout.activity_cube);
SurfaceView sv = findViewById(R.id.surfaceView);
sv.getHolder().addCallback(new CubeRenderer());
sv.getHolder().addCallback(new CubeRenderer(getResources()));
}
}

View File

@ -15,7 +15,6 @@ import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.ImageView;
import java.io.InputStream;
import org.skia.androidkit.*;
public class MainActivity extends Activity implements SurfaceHolder.Callback {
@ -55,11 +54,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback {
canvas.drawImage(snapshot, 0, 200);
try {
InputStream is = getResources().openRawResource(R.raw.brickwork_texture);
byte[] data = new byte[is.available()];
is.read(data);
Image image = Image.fromEncoded(data);
Image image = Image.fromStream(getResources().openRawResource(R.raw.brickwork_texture));
// TODO: Canvas.scale
canvas.concat(new Matrix().scale(10, 10));
canvas.drawImage(image, 20, 0, SamplingOptions.CATMULLROM());