Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

native returns unpremultiplied data #1765

Merged
merged 2 commits into from Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions cocos/platform/android/CCCanvasRenderingContext2D-android.cpp
Expand Up @@ -4,6 +4,7 @@

#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
#include "platform/android/jni/JniHelper.h"
#include "platform/android/jni/JniImp.h"

#include <regex>

Expand Down Expand Up @@ -214,12 +215,35 @@ class CanvasRenderingContext2DImpl
return _data;
}

#define CLAMP(V, LO, HI) std::min(std::max( (V), (LO) ), (HI) )
void unMultiplyAlpha(unsigned char* ptr, ssize_t size)
{
// Android source data is not premultiplied alpha when API >= 19
// please refer CanvasRenderingContext2DImpl::recreateBuffer(float w, float h)
// in CanvasRenderingContext2DImpl.java
// if (getAndroidSDKInt() >= 19)
// return;

char alpha;
for (int i = 0; i < size; i += 4)
{
alpha = ptr[i + 3];
if (alpha > 0)
{
ptr[i] = CLAMP(ptr[i] / alpha * 255, 0, 255);
ptr[i+1] = CLAMP(ptr[i+1] / alpha * 255, 0, 255);
ptr[i+2] = CLAMP(ptr[i+2] / alpha * 255, 0, 255);
}
}
}

void fillData()
{
jbyteArray arr = JniHelper::callObjectByteArrayMethod(_obj, JCLS_CANVASIMPL, "getDataRef");
jsize len = JniHelper::getEnv()->GetArrayLength(arr);
jbyte* jbarray = (jbyte *)malloc(len * sizeof(jbyte));
JniHelper::getEnv()->GetByteArrayRegion(arr,0,len,jbarray);
unMultiplyAlpha( (unsigned char*) jbarray, len);
_data.fastSet((unsigned char*) jbarray, len); //IDEA: DON'T create new jbarray every time.
JniHelper::getEnv()->DeleteLocalRef(arr);
}
Expand Down
Expand Up @@ -210,6 +210,14 @@ private void recreateBuffer(float w, float h) {
mBitmap.recycle();
}
mBitmap = Bitmap.createBitmap((int)Math.ceil(w), (int)Math.ceil(h), Bitmap.Config.ARGB_8888);
// FIXME: in MIX 2S, its API level is 28, but can not find invokeInstanceMethod. It seems
// devices may not obey the specification, so comment the codes.
// if (Build.VERSION.SDK_INT >= 19) {
// Cocos2dxReflectionHelper.<Void>invokeInstanceMethod(mBitmap,
// "setPremultiplied",
// new Class[]{Boolean.class},
// new Object[]{Boolean.FALSE});
// }
mCanvas.setBitmap(mBitmap);
}

Expand Down Expand Up @@ -491,10 +499,12 @@ else if (mTextBaseline == TEXT_BASELINE_MIDDLE)
private byte[] getDataRef() {
// Log.d(TAG, "this: " + this + ", getDataRef ...");
if (mBitmap != null) {
final byte[] pixels = new byte[mBitmap.getWidth() * mBitmap.getHeight() * 4];
final int len = mBitmap.getWidth() * mBitmap.getHeight() * 4;
final byte[] pixels = new byte[len];
final ByteBuffer buf = ByteBuffer.wrap(pixels);
buf.order(ByteOrder.nativeOrder());
mBitmap.copyPixelsToBuffer(buf);

return pixels;
}

Expand Down
25 changes: 25 additions & 0 deletions cocos/platform/android/jni/JniImp.cpp
Expand Up @@ -101,6 +101,7 @@ namespace
int g_height = 0;
bool g_isStarted = false;
bool g_isGameFinished = false;
int g_SDKInt = 0;

cocos2d::Application* g_app = nullptr;

Expand All @@ -127,10 +128,29 @@ Application* cocos_android_app_init(JNIEnv* env, int width, int height);

extern "C"
{
void getSDKInt(JNIEnv* env)
{
if (env && g_SDKInt == 0)
{
// VERSION is a nested class within android.os.Build (hence "$" rather than "/")
jclass versionClass = env->FindClass("android/os/Build$VERSION");
if (NULL == versionClass)
return;

jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
if (NULL == sdkIntFieldID)
return;

g_SDKInt = env->GetStaticIntField(versionClass, sdkIntFieldID);
}
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JniHelper::setJavaVM(vm);
cocos_jni_env_init(JniHelper::getEnv());
getSDKInt(JniHelper::getEnv());

return JNI_VERSION_1_4;
}

Expand Down Expand Up @@ -647,4 +667,9 @@ bool getApplicationExited()
return g_isGameFinished;
}

int getAndroidSDKInt()
{
return g_SDKInt;
}


1 change: 1 addition & 0 deletions cocos/platform/android/jni/JniImp.h
Expand Up @@ -50,3 +50,4 @@ extern void setGameInfoDebugViewTextJNI(int index, const std::string& text);
extern void setJSBInvocationCountJNI(int count);
extern void openDebugViewJNI();
extern void disableBatchGLCommandsToNativeJNI();
extern int getAndroidSDKInt();
112 changes: 51 additions & 61 deletions cocos/platform/apple/CCCanvasRenderingContext2D-apple.mm
Expand Up @@ -23,7 +23,6 @@
#endif

#include <regex>

enum class CanvasTextAlign {
LEFT,
CENTER,
Expand Down Expand Up @@ -320,7 +319,7 @@ -(NSPoint) convertDrawPoint:(NSPoint) point text:(NSString*) text {
return point;
}

-(void) fillText:(NSString*) text x:(CGFloat) x y:(CGFloat) y maxWidth:(CGFloat) maxWidth {
-(void) drawText:(NSString*)text x:(CGFloat)x y:(CGFloat)y maxWidth:(CGFloat)maxWidth isStroke:(BOOL)isStroke {
if (text.length == 0)
return;

Expand All @@ -330,69 +329,43 @@ -(void) fillText:(NSString*) text x:(CGFloat) x y:(CGFloat) y maxWidth:(CGFloat)
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;

[_tokenAttributesDict removeObjectForKey:NSStrokeWidthAttributeName];
[_tokenAttributesDict removeObjectForKey:NSStrokeColorAttributeName];

[_tokenAttributesDict setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
[_tokenAttributesDict setObject:[NSColor colorWithRed:_fillStyle.r green:_fillStyle.g blue:_fillStyle.b alpha:_fillStyle.a]
forKey:NSForegroundColorAttributeName];
[_tokenAttributesDict setObject:[NSColor colorWithRed:_fillStyle.r
green:_fillStyle.g
blue:_fillStyle.b
alpha:_fillStyle.a]
forKey:NSForegroundColorAttributeName];

[self saveContext];

// text color
CGContextSetRGBFillColor(_context, _fillStyle.r, _fillStyle.g, _fillStyle.b, _fillStyle.a);
CGContextSetShouldSubpixelQuantizeFonts(_context, false);
CGContextBeginTransparencyLayerWithRect(_context, CGRectMake(0, 0, _width, _height), nullptr);
CGContextSetTextDrawingMode(_context, kCGTextFill);


if (isStroke)
{
CGContextSetLineWidth(_context, _lineWidth);
CGContextSetLineJoin(_context, kCGLineJoinRound);
CGContextSetTextDrawingMode(_context, kCGTextStroke);
}
else
CGContextSetTextDrawingMode(_context, kCGTextFill);

NSAttributedString *stringWithAttributes =[[[NSAttributedString alloc] initWithString:text
attributes:_tokenAttributesDict] autorelease];

[stringWithAttributes drawAtPoint:drawPoint];


CGContextEndTransparencyLayer(_context);

[self restoreContext];
}

-(void) strokeText:(NSString*) text x:(CGFloat) x y:(CGFloat) y maxWidth:(CGFloat) maxWidth {
if (text.length == 0)
return;

NSPoint drawPoint = [self convertDrawPoint:NSMakePoint(x, y) text:text];

NSMutableParagraphStyle* paragraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;

[_tokenAttributesDict removeObjectForKey:NSForegroundColorAttributeName];

[_tokenAttributesDict setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
[_tokenAttributesDict setObject:[NSColor colorWithRed:_strokeStyle.r
green:_strokeStyle.g
blue:_strokeStyle.b
alpha:_strokeStyle.a] forKey:NSStrokeColorAttributeName];

[self saveContext];

// text color
CGContextSetRGBFillColor(_context, _fillStyle.r, _fillStyle.g, _fillStyle.b, _fillStyle.a);
CGContextSetLineWidth(_context, _lineWidth);
CGContextSetLineJoin(_context, kCGLineJoinRound);
CGContextSetShouldSubpixelQuantizeFonts(_context, false);
CGContextBeginTransparencyLayerWithRect(_context, CGRectMake(0, 0, _width, _height), nullptr);

CGContextSetTextDrawingMode(_context, kCGTextStroke);

NSAttributedString *stringWithAttributes =[[[NSAttributedString alloc] initWithString:text
attributes:_tokenAttributesDict] autorelease];

[stringWithAttributes drawAtPoint:drawPoint];

CGContextEndTransparencyLayer(_context);
-(void) fillText:(NSString*) text x:(CGFloat) x y:(CGFloat) y maxWidth:(CGFloat) maxWidth {
[self drawText:text x:x y:y maxWidth:maxWidth isStroke:FALSE];
}

[self restoreContext];
-(void) strokeText:(NSString*) text x:(CGFloat) x y:(CGFloat) y maxWidth:(CGFloat) maxWidth {
[self drawText:text x:x y:y maxWidth:maxWidth isStroke:TRUE];
}

-(void) setFillStyleWithRed:(CGFloat) r green:(CGFloat) g blue:(CGFloat) b alpha:(CGFloat) a {
Expand Down Expand Up @@ -529,6 +502,33 @@ -(void) lineToX: (float) x y:(float) y {

// CanvasRenderingContext2D

namespace
{
#define CLAMP(V, LO, HI) std::min(std::max( (V), (LO) ), (HI) )
void unMultiplyAlpha(unsigned char* ptr, ssize_t size)
{
char alpha;
for (int i = 0; i < size; i += 4)
{
alpha = ptr[i + 3];
if (alpha > 0)
{
ptr[i] = CLAMP(ptr[i] / alpha * 255, 0, 255);
ptr[i+1] = CLAMP(ptr[i+1] / alpha * 255, 0, 255);
ptr[i+2] = CLAMP(ptr[i+2] / alpha * 255, 0, 255);
}
}
}
}

#define SEND_DATA_TO_JS(CB, IMPL) \
if (CB) \
{ \
auto& data = [IMPL getDataRef]; \
unMultiplyAlpha(data.getBytes(), data.getSize() ); \
CB(data); \
}

CanvasRenderingContext2D::CanvasRenderingContext2D(float width, float height)
: __width(width)
, __height(height)
Expand All @@ -551,8 +551,7 @@ -(void) lineToX: (float) x y:(float) y {
_isBufferSizeDirty = false;
// SE_LOGD("CanvasRenderingContext2D::recreateBufferIfNeeded %p, w: %f, h:%f\n", this, __width, __height);
[_impl recreateBufferWithWidth: __width height:__height];
if (_canvasBufferUpdatedCB != nullptr)
_canvasBufferUpdatedCB([_impl getDataRef]);
SEND_DATA_TO_JS(_canvasBufferUpdatedCB, _impl);
}
}

Expand All @@ -567,11 +566,7 @@ -(void) lineToX: (float) x y:(float) y {
{
recreateBufferIfNeeded();
[_impl fillRect:CGRectMake(x, y, width, height)];

if (_canvasBufferUpdatedCB != nullptr)
{
_canvasBufferUpdatedCB([_impl getDataRef]);
}
SEND_DATA_TO_JS(_canvasBufferUpdatedCB, _impl);
}

void CanvasRenderingContext2D::fillText(const std::string& text, float x, float y, float maxWidth)
Expand All @@ -583,8 +578,7 @@ -(void) lineToX: (float) x y:(float) y {
recreateBufferIfNeeded();

[_impl fillText:[NSString stringWithUTF8String:text.c_str()] x:x y:y maxWidth:maxWidth];
if (_canvasBufferUpdatedCB != nullptr)
_canvasBufferUpdatedCB([_impl getDataRef]);
SEND_DATA_TO_JS(_canvasBufferUpdatedCB, _impl);
}

void CanvasRenderingContext2D::strokeText(const std::string& text, float x, float y, float maxWidth)
Expand All @@ -595,9 +589,7 @@ -(void) lineToX: (float) x y:(float) y {
recreateBufferIfNeeded();

[_impl strokeText:[NSString stringWithUTF8String:text.c_str()] x:x y:y maxWidth:maxWidth];

if (_canvasBufferUpdatedCB != nullptr)
_canvasBufferUpdatedCB([_impl getDataRef]);
SEND_DATA_TO_JS(_canvasBufferUpdatedCB, _impl);
}

cocos2d::Size CanvasRenderingContext2D::measureText(const std::string& text)
Expand Down Expand Up @@ -640,9 +632,7 @@ -(void) lineToX: (float) x y:(float) y {
void CanvasRenderingContext2D::stroke()
{
[_impl stroke];

if (_canvasBufferUpdatedCB != nullptr)
_canvasBufferUpdatedCB([_impl getDataRef]);
SEND_DATA_TO_JS(_canvasBufferUpdatedCB, _impl);
}

void CanvasRenderingContext2D::fill()
Expand Down
15 changes: 7 additions & 8 deletions cocos/platform/win32/CCCanvasRenderingContext2D-win32.cpp
Expand Up @@ -156,10 +156,9 @@ class CanvasRenderingContext2DImpl
uint8_t* buffer = _imageData.getBytes();
if (buffer)
{
float alpha = _fillStyle.a;
uint8_t r = _fillStyle.r * 255.0f * alpha;
uint8_t g = _fillStyle.g * 255.0f * alpha;
uint8_t b = _fillStyle.b * 255.0f * alpha;
uint8_t r = _fillStyle.r * 255.0f;
uint8_t g = _fillStyle.g * 255.0f;
uint8_t b = _fillStyle.b * 255.0f;
uint8_t a = _fillStyle.a * 255.0f;
fillRectWithColor(buffer, (uint32_t)_bufferWidth, (uint32_t)_bufferHeight, (uint32_t)x, (uint32_t)y, (uint32_t)w, (uint32_t)h, r, g, b, a);
}
Expand Down Expand Up @@ -556,10 +555,10 @@ class CanvasRenderingContext2DImpl
// "dirtyValue > 0" means pixel was covered when drawing text
if (dirtyValue > 0)
{
// r = _fillStyle.r * 255 * (dirtyValue / 255) * alpha;
r = _fillStyle.r * dirtyValue * alpha;
g = _fillStyle.g * dirtyValue * alpha;
b = _fillStyle.b * dirtyValue * alpha;
// r = _fillStyle.r * 255 * (dirtyValue / 255);
r = _fillStyle.r * dirtyValue;
g = _fillStyle.g * dirtyValue;
b = _fillStyle.b * dirtyValue;
COLORREF textColor = (b << 16 | g << 8 | r) & 0x00ffffff;
val = ((BYTE)(dirtyValue * alpha) << 24) | textColor;
}
Expand Down