Skip to content

Commit

Permalink
native returns unpremultiplied data (#1765)
Browse files Browse the repository at this point in the history
* unpremultiply alpha on ios/mac

* unpremultiply alpha on Android and windows
  • Loading branch information
minggo committed Jul 2, 2019
1 parent 8d54569 commit ad52fe3
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 70 deletions.
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 @@ -487,10 +495,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 @@ -314,7 +313,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 @@ -324,69 +323,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 @@ -523,6 +496,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 @@ -545,8 +545,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 @@ -561,11 +560,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 @@ -577,8 +572,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 @@ -589,9 +583,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 @@ -634,9 +626,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 @@ -558,10 +557,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

0 comments on commit ad52fe3

Please sign in to comment.