Skip to content

Commit bb27140

Browse files
rh101minggo
authored andcommitted
Added support for saving images with non-premultiplied alpha (#19782)
* Added RenderTexture::saveToFileAsNonPMA() to save images without PMA. Set the PMA parameter to true when calling initWithRawData() inside RenderTexture::newImage(), since textures are PMA. Renamed Image::premultipliedAlpha() to Image::premultiplyAlpha() to better reflect it's action, and made it public. Added Image::reversePremultipliedAlpha() to allow the reversing of the PMA. Updated CCImage-ios.mm to set the correct bitmapInfo for PMA and non-PMA images before saving a file. Updated RenderTextureTest::RenderTextureSave() to cater for non-PMA file saving. * [CCImage-ios.mm] Fixed indentation.
1 parent 82763f8 commit bb27140

File tree

7 files changed

+168
-20
lines changed

7 files changed

+168
-20
lines changed

cocos/2d/CCRenderTexture.cpp

+46-4
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,28 @@ void RenderTexture::visit(Renderer *renderer, const Mat4 &parentTransform, uint3
506506
// setOrderOfArrival(0);
507507
}
508508

509+
bool RenderTexture::saveToFileAsNonPMA(const std::string& filename, bool isRGBA, std::function<void(RenderTexture*, const std::string&)> callback)
510+
{
511+
std::string basename(filename);
512+
std::transform(basename.begin(), basename.end(), basename.begin(), ::tolower);
513+
514+
if (basename.find(".png") != std::string::npos)
515+
{
516+
return saveToFileAsNonPMA(filename, Image::Format::PNG, isRGBA, callback);
517+
}
518+
else if (basename.find(".jpg") != std::string::npos)
519+
{
520+
if (isRGBA) CCLOG("RGBA is not supported for JPG format.");
521+
return saveToFileAsNonPMA(filename, Image::Format::JPG, false, callback);
522+
}
523+
else
524+
{
525+
CCLOG("Only PNG and JPG format are supported now!");
526+
}
527+
528+
return saveToFileAsNonPMA(filename, Image::Format::JPG, false, callback);
529+
}
530+
509531
bool RenderTexture::saveToFile(const std::string& filename, bool isRGBA, std::function<void (RenderTexture*, const std::string&)> callback)
510532
{
511533
std::string basename(filename);
@@ -528,6 +550,22 @@ bool RenderTexture::saveToFile(const std::string& filename, bool isRGBA, std::fu
528550
return saveToFile(filename, Image::Format::JPG, false, callback);
529551
}
530552

553+
bool RenderTexture::saveToFileAsNonPMA(const std::string& fileName, Image::Format format, bool isRGBA, std::function<void(RenderTexture*, const std::string&)> callback)
554+
{
555+
CCASSERT(format == Image::Format::JPG || format == Image::Format::PNG,
556+
"the image can only be saved as JPG or PNG format");
557+
if (isRGBA && format == Image::Format::JPG) CCLOG("RGBA is not supported for JPG format");
558+
559+
_saveFileCallback = callback;
560+
561+
std::string fullpath = FileUtils::getInstance()->getWritablePath() + fileName;
562+
_saveToFileCommand.init(_globalZOrder);
563+
_saveToFileCommand.func = CC_CALLBACK_0(RenderTexture::onSaveToFile, this, fullpath, isRGBA, true);
564+
565+
Director::getInstance()->getRenderer()->addCommand(&_saveToFileCommand);
566+
return true;
567+
}
568+
531569
bool RenderTexture::saveToFile(const std::string& fileName, Image::Format format, bool isRGBA, std::function<void (RenderTexture*, const std::string&)> callback)
532570
{
533571
CCASSERT(format == Image::Format::JPG || format == Image::Format::PNG,
@@ -538,17 +576,21 @@ bool RenderTexture::saveToFile(const std::string& fileName, Image::Format format
538576

539577
std::string fullpath = FileUtils::getInstance()->getWritablePath() + fileName;
540578
_saveToFileCommand.init(_globalZOrder);
541-
_saveToFileCommand.func = CC_CALLBACK_0(RenderTexture::onSaveToFile, this, fullpath, isRGBA);
579+
_saveToFileCommand.func = CC_CALLBACK_0(RenderTexture::onSaveToFile, this, fullpath, isRGBA, false);
542580

543581
Director::getInstance()->getRenderer()->addCommand(&_saveToFileCommand);
544582
return true;
545583
}
546584

547-
void RenderTexture::onSaveToFile(const std::string& filename, bool isRGBA)
585+
void RenderTexture::onSaveToFile(const std::string& filename, bool isRGBA, bool forceNonPMA)
548586
{
549587
Image *image = newImage(true);
550588
if (image)
551589
{
590+
if (forceNonPMA && image->hasPremultipliedAlpha())
591+
{
592+
image->reversePremultipliedAlpha();
593+
}
552594
image->saveToFile(filename, !isRGBA);
553595
}
554596
if(_saveFileCallback)
@@ -620,11 +662,11 @@ Image* RenderTexture::newImage(bool flipImage)
620662
savedBufferWidth * 4);
621663
}
622664

623-
image->initWithRawData(buffer, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8);
665+
image->initWithRawData(buffer, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8, true);
624666
}
625667
else
626668
{
627-
image->initWithRawData(tempData, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8);
669+
image->initWithRawData(tempData, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8, true);
628670
}
629671

630672
} while (0);

cocos/2d/CCRenderTexture.h

+26-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ class CC_DLL RenderTexture : public Node
151151

152152
CC_DEPRECATED_ATTRIBUTE Image* newCCImage(bool flipImage = true) { return newImage(flipImage); };
153153

154+
/** Saves the texture into a file using JPEG format. The file will be saved in the Documents folder.
155+
* Returns true if the operation is successful.
156+
*
157+
* @param filename The file name.
158+
* @param isRGBA The file is RGBA or not.
159+
* @param callback When the file is save finished,it will callback this function.
160+
* @return Returns true if the operation is successful.
161+
*/
162+
bool saveToFileAsNonPMA(const std::string& filename, bool isRGBA = true, std::function<void(RenderTexture*, const std::string&)> callback = nullptr);
163+
164+
154165
/** Saves the texture into a file using JPEG format. The file will be saved in the Documents folder.
155166
* Returns true if the operation is successful.
156167
*
@@ -161,6 +172,20 @@ class CC_DLL RenderTexture : public Node
161172
*/
162173
bool saveToFile(const std::string& filename, bool isRGBA = true, std::function<void (RenderTexture*, const std::string&)> callback = nullptr);
163174

175+
/** saves the texture into a file in non-PMA. The format could be JPG or PNG. The file will be saved in the Documents folder.
176+
Returns true if the operation is successful.
177+
* Notes: since v3.x, saveToFile will generate a custom command, which will be called in the following render->render().
178+
* So if this function is called in a event handler, the actual save file will be called in the next frame. If we switch to a different scene, the game will crash.
179+
* To solve this, add Director::getInstance()->getRenderer()->render(); after this function.
180+
*
181+
* @param filename The file name.
182+
* @param format The image format.
183+
* @param isRGBA The file is RGBA or not.
184+
* @param callback When the file is save finished,it will callback this function.
185+
* @return Returns true if the operation is successful.
186+
*/
187+
bool saveToFileAsNonPMA(const std::string& fileName, Image::Format format, bool isRGBA, std::function<void(RenderTexture*, const std::string&)> callback);
188+
164189
/** saves the texture into a file. The format could be JPG or PNG. The file will be saved in the Documents folder.
165190
Returns true if the operation is successful.
166191
* Notes: since v3.x, saveToFile will generate a custom command, which will be called in the following render->render().
@@ -363,7 +388,7 @@ class CC_DLL RenderTexture : public Node
363388
void onClear();
364389
void onClearDepth();
365390

366-
void onSaveToFile(const std::string& fileName, bool isRGBA = true);
391+
void onSaveToFile(const std::string& fileName, bool isRGBA = true, bool forceNonPMA = false);
367392

368393
void setupDepthAndStencil(int powW, int powH);
369394

cocos/platform/CCImage.cpp

+25-2
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ bool Image::initWithPngData(const unsigned char * data, ssize_t dataLen)
11491149
{
11501150
if (PNG_PREMULTIPLIED_ALPHA_ENABLED)
11511151
{
1152-
premultipliedAlpha();
1152+
premultiplyAlpha();
11531153
}
11541154
else
11551155
{
@@ -2454,7 +2454,7 @@ bool Image::saveImageToJPG(const std::string& filePath)
24542454
#endif // CC_USE_JPEG
24552455
}
24562456

2457-
void Image::premultipliedAlpha()
2457+
void Image::premultiplyAlpha()
24582458
{
24592459
#if CC_ENABLE_PREMULTIPLIED_ALPHA == 0
24602460
_hasPremultipliedAlpha = false;
@@ -2473,6 +2473,29 @@ void Image::premultipliedAlpha()
24732473
#endif
24742474
}
24752475

2476+
static inline unsigned char clamp(int x) {
2477+
return (unsigned char)(x >= 0 ? (x < 255 ? x : 255) : 0);
2478+
}
2479+
2480+
void Image::reversePremultipliedAlpha()
2481+
{
2482+
CCASSERT(_renderFormat == Texture2D::PixelFormat::RGBA8888, "The pixel format should be RGBA8888!");
2483+
2484+
unsigned int* fourBytes = (unsigned int*)_data;
2485+
for (int i = 0; i < _width * _height; i++)
2486+
{
2487+
unsigned char* p = _data + i * 4;
2488+
if (p[3] > 0)
2489+
{
2490+
fourBytes[i] = clamp(int(std::ceil((p[0] * 255.0f) / p[3]))) |
2491+
clamp(int(std::ceil((p[1] * 255.0f) / p[3]))) << 8 |
2492+
clamp(int(std::ceil((p[2] * 255.0f) / p[3]))) << 16 |
2493+
p[3] << 24;
2494+
}
2495+
}
2496+
2497+
_hasPremultipliedAlpha = false;
2498+
}
24762499

24772500
void Image::setPVRImagesHavePremultipliedAlpha(bool haveAlphaPremultiplied)
24782501
{

cocos/platform/CCImage.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ class CC_DLL Image : public Ref
160160
@param isToRGB whether the image is saved as RGB format.
161161
*/
162162
bool saveToFile(const std::string &filename, bool isToRGB = true);
163+
void premultiplyAlpha();
164+
void reversePremultipliedAlpha();
163165

164166
protected:
165167
#if CC_USE_WIC
@@ -182,8 +184,6 @@ class CC_DLL Image : public Ref
182184
bool saveImageToPNG(const std::string& filePath, bool isToRGB = true);
183185
bool saveImageToJPG(const std::string& filePath);
184186

185-
void premultipliedAlpha();
186-
187187
protected:
188188
/**
189189
@brief Determine how many mipmaps can we have.

cocos/platform/ios/CCImage-ios.mm

+8-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ of this software and associated documentation files (the "Software"), to deal
9191
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
9292
if (saveToPNG && hasAlpha() && (! isToRGB))
9393
{
94-
bitmapInfo |= kCGImageAlphaPremultipliedLast;
94+
if (_hasPremultipliedAlpha)
95+
{
96+
bitmapInfo |= kCGImageAlphaPremultipliedLast;
97+
}
98+
else
99+
{
100+
bitmapInfo |= kCGImageAlphaLast;
101+
}
95102
}
96103
CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, pixels, myDataLength, nullptr);
97104
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();

tests/cpp-tests/Classes/RenderTextureTest/RenderTextureTest.cpp

+56-9
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,15 @@ RenderTextureSave::RenderTextureSave()
6262

6363
// Save Image menu
6464
MenuItemFont::setFontSize(16);
65-
auto item1 = MenuItemFont::create("Save Image", CC_CALLBACK_1(RenderTextureSave::saveImage, this));
66-
auto item2 = MenuItemFont::create("Clear", CC_CALLBACK_1(RenderTextureSave::clearImage, this));
67-
auto menu = Menu::create(item1, item2, nullptr);
65+
auto item1 = MenuItemFont::create("Save Image PMA", CC_CALLBACK_1(RenderTextureSave::saveImageWithPremultipliedAlpha, this));
66+
auto item2 = MenuItemFont::create("Save Image Non-PMA", CC_CALLBACK_1(RenderTextureSave::saveImageWithNonPremultipliedAlpha, this));
67+
auto item3 = MenuItemFont::create("Add Image", CC_CALLBACK_1(RenderTextureSave::addImage, this));
68+
auto item4 = MenuItemFont::create("Clear to Random", CC_CALLBACK_1(RenderTextureSave::clearImage, this));
69+
auto item5 = MenuItemFont::create("Clear to Transparent", CC_CALLBACK_1(RenderTextureSave::clearImageTransparent, this));
70+
auto menu = Menu::create(item1, item2, item3, item4, item5, nullptr);
6871
this->addChild(menu);
6972
menu->alignItemsVertically();
70-
menu->setPosition(Vec2(VisibleRect::rightTop().x - 80, VisibleRect::rightTop().y - 30));
73+
menu->setPosition(Vec2(VisibleRect::rightTop().x - 80, VisibleRect::rightTop().y - 100));
7174
}
7275

7376
std::string RenderTextureSave::title() const
@@ -80,17 +83,46 @@ std::string RenderTextureSave::subtitle() const
8083
return "Press 'Save Image' to create an snapshot of the render texture";
8184
}
8285

83-
void RenderTextureSave::clearImage(cocos2d::Ref *sender)
86+
void RenderTextureSave::clearImage(cocos2d::Ref* sender)
8487
{
8588
_target->clear(CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1());
8689
}
8790

88-
void RenderTextureSave::saveImage(cocos2d::Ref *sender)
91+
void RenderTextureSave::clearImageTransparent(cocos2d::Ref* sender)
92+
{
93+
_target->clear(0, 0, 0, 0);
94+
}
95+
96+
void RenderTextureSave::saveImageWithPremultipliedAlpha(cocos2d::Ref* sender)
97+
{
98+
static int counter = 0;
99+
100+
char png[20];
101+
sprintf(png, "image-pma-%d.png", counter);
102+
103+
auto callback = [&](RenderTexture* rt, const std::string& path)
104+
{
105+
auto sprite = Sprite::create(path);
106+
addChild(sprite);
107+
sprite->setScale(0.3f);
108+
sprite->setPosition(Vec2(40, 40));
109+
sprite->setRotation(counter * 3);
110+
};
111+
112+
_target->saveToFile(png, Image::Format::PNG, true, callback);
113+
//Add this function to avoid crash if we switch to a new scene.
114+
Director::getInstance()->getRenderer()->render();
115+
CCLOG("Image saved %s", png);
116+
117+
counter++;
118+
}
119+
120+
void RenderTextureSave::saveImageWithNonPremultipliedAlpha(cocos2d::Ref *sender)
89121
{
90122
static int counter = 0;
91123

92124
char png[20];
93-
sprintf(png, "image-%d.png", counter);
125+
sprintf(png, "image-no-pma-%d.png", counter);
94126

95127
auto callback = [&](RenderTexture* rt, const std::string& path)
96128
{
@@ -101,14 +133,29 @@ void RenderTextureSave::saveImage(cocos2d::Ref *sender)
101133
sprite->setRotation(counter * 3);
102134
};
103135

104-
_target->saveToFile(png, Image::Format::PNG, true, callback);
136+
_target->saveToFileAsNonPMA(png, Image::Format::PNG, true, callback);
105137
//Add this function to avoid crash if we switch to a new scene.
106138
Director::getInstance()->getRenderer()->render();
107139
CCLOG("Image saved %s", png);
108140

109141
counter++;
110142
}
111143

144+
void RenderTextureSave::addImage(cocos2d::Ref* sender)
145+
{
146+
auto s = Director::getInstance()->getWinSize();
147+
148+
// begin drawing to the render texture
149+
_target->begin();
150+
151+
Sprite* sprite = Sprite::create("Images/test-rgba1.png");
152+
sprite->setPosition(sprite->getContentSize().width + CCRANDOM_0_1() * (s.width - sprite->getContentSize().width), sprite->getContentSize().height + CCRANDOM_0_1() * (s.height - sprite->getContentSize().height));
153+
sprite->visit();
154+
155+
// finish drawing and return context back to the screen
156+
_target->end();
157+
}
158+
112159
RenderTextureSave::~RenderTextureSave()
113160
{
114161
_target->release();
@@ -849,4 +896,4 @@ std::string RenderTextureWithSprite3DIssue16894::title() const
849896
std::string RenderTextureWithSprite3DIssue16894::subtitle() const
850897
{
851898
return "3 ships, 1st & 3rd are the same";
852-
}
899+
}

tests/cpp-tests/Classes/RenderTextureTest/RenderTextureTest.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ class RenderTextureSave : public RenderTextureTest
4444
virtual std::string subtitle() const override;
4545
void onTouchesMoved(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event* event);
4646
void clearImage(cocos2d::Ref* pSender);
47-
void saveImage(cocos2d::Ref* pSender);
47+
void clearImageTransparent(cocos2d::Ref* sender);
48+
void saveImageWithPremultipliedAlpha(cocos2d::Ref* pSender);
49+
void saveImageWithNonPremultipliedAlpha(cocos2d::Ref* pSender);
50+
51+
void addImage(cocos2d::Ref* sender);
4852

4953
private:
5054
cocos2d::RenderTexture* _target;

0 commit comments

Comments
 (0)