I'm experimenting SDL2 and I encounter a case I cannot manage correctly when I draw on an intermediate transparent texture.
我正在试验SDL2,我遇到一个案例,当我在中间透明纹理上绘制时,我无法正确管理。
So if I represent this as layers it's as follow
因此,如果我将其表示为图层,则如下所示
img_w_A_channel img_w_A_channel img_w_A_channel img_w_A_channel
solid_texture half_transparent transparent_texture
window window window window
- On the left, I render the image directly on the window.
- 在左侧,我直接在窗口上渲染图像。
-
solid_texture
means a black colored intermediate texture withalpha=255
where I draw the image in then I draw the texture on the window. - solid_texture表示黑色的中间纹理,alpha = 255,我绘制图像然后在窗口上绘制纹理。
-
half_transparent
means a black colored intermediate texture withalpha=128
where I draw the image in then I draw the texture on the window. - half_transparent表示黑色的中间纹理,alpha = 128,我在那里绘制图像,然后在窗口上绘制纹理。
-
transparent_texture
means a black colored intermediate texture withalpha=0
where I draw the image in then I draw the texture on the window. - transparent_texture表示一个黑色的中间纹理,alpha = 0,我绘制图像然后在窗口上绘制纹理。
So think about the intermediate texture as a container I want to draw in. So as the window can have a background texture like here, SDL_BLENDMODE_NONE
is not an option for the intermediate texture.
所以考虑将中间纹理作为我想要绘制的容器。因为窗口可以有像这里的背景纹理,SDL_BLENDMODE_NONE不是中间纹理的选项。
And the result is:
结果是:
As you can see the first and second image (drawn on a solid intermediate texture are identical. The third and fourth image (drawn on semi-transparent and transparent intermediate texture) are not identical.
As you may guess, I would like to get with the image on the right the same result as with the image on the left. i.e. being able to draw in a transparent container and get the same result as drawing directly on the window.
正如你所看到的那样,第一张和第二张图片(在一个坚实的中间纹理上绘制是相同的。第三张和第四张图片(在半透明和透明的中间纹理上绘制)并不完全相同。正如你可能猜到的,我想得到的右边的图像与左边的图像结果相同,即能够在透明容器中绘制并获得与直接在窗口上绘制相同的结果。
And the image drawn is here and the background is optional as it doesn't change anything in the result so you can remove the corresponding code.
并且绘制的图像在这里,背景是可选的,因为它不会更改结果中的任何内容,因此您可以删除相应的代码。
My full test code is:
我的完整测试代码是:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *background = NULL;
SDL_Texture *image = NULL;
SDL_Surface* surface = NULL;
SDL_Rect dst = {20, 20, 100, 100};
#define SOLID 255
#define HALF_TRANSPARENT 128
#define TRANSPARENT 0
SDL_Texture * renderImageInATexture(SDL_Texture *image, Uint8 transparency) {
// Create an intermediate texture
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 200, 200);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(renderer, texture);
// Paint it
SDL_SetRenderDrawColor(renderer, (Uint8) 0, (Uint8) 0, (Uint8) 0,(Uint8) transparency);
SDL_RenderClear(renderer);
// Render the image in intermediate texture
SDL_SetRenderTarget(renderer, texture);
SDL_RenderCopy( renderer, image, NULL, &dst );
SDL_SetRenderTarget(renderer, NULL);
return texture;
}
int main(char *argc, char **argv) {
SDL_Init( SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(
800,
200,
SDL_WINDOW_SHOWN,
&window,
&renderer);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(renderer, NULL);
// Fill the window with a texture for fun (doesn't change anything on the problem) can be removed ....
surface = IMG_Load( "./carbone.png" );
background = SDL_CreateTextureFromSurface( renderer, surface );
SDL_FreeSurface( surface );
// draw background in main display
SDL_RenderCopy( renderer, background, NULL, NULL );
// .... until here
surface = IMG_Load( "./image.png" );
image = SDL_CreateTextureFromSurface( renderer, surface );
SDL_FreeSurface( surface );
// draw imagein main display
SDL_RenderCopy( renderer, image, NULL, &dst );
SDL_Texture *texture = renderImageInATexture(image, SOLID);
// render intermediate texture in main display
SDL_Rect dstTexture = {200, 0, 200, 200};
SDL_RenderCopy( renderer, texture, NULL, &dstTexture );
SDL_Texture *texture2 = renderImageInATexture(image, HALF_TRANSPARENT);
// render intermediate texture2 in main display
SDL_Rect dstTexture2 = {400, 0, 200, 200};
SDL_RenderCopy( renderer, texture2, NULL, &dstTexture2 );
SDL_Texture *texture3 = renderImageInATexture(image, TRANSPARENT);
// render intermediate texture3 in main display
SDL_Rect dstTexture3 = {600, 0, 200, 200};
SDL_RenderCopy( renderer, texture3, NULL, &dstTexture3 );
SDL_RenderPresent(renderer);
SDL_Delay(10000);
SDL_DestroyTexture(image);
SDL_DestroyTexture(bakcground); // only if you use a background
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
And to build it:
并建立它:
cc main.c -lSDL2 -lSDL2_image
Built with SDL 2.0.8 but it doesn't matter.
使用SDL 2.0.8构建但无关紧要。
Any idea to fix this problem?
有什么想法解决这个问题吗?
1 个解决方案
#1
0
All right, the thing is that blendmode_blend works like
好吧,问题是blendmode_blend就像
dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
dstA = srcA + (dstA * (1-srcA))
Now since your alpha is less than < 1 (1 being 255 in SDL scale) your dstRGB gets attenuated (ie lower values). For example using your circle's border you get that RGBA all are equal to 38, scaling this is alpha ~ 0.149, which gives and end result of RGBA ~ 5 (38 * 0.149).
现在,由于您的alpha小于<1(SDL标度为1),因此dstRGB会衰减(即较低的值)。例如,使用你的圆圈边界,你得到的RGBA都等于38,缩放这是alpha~0.149,这给出了RGBA~5(38 * 0.149)的最终结果。
Since your image also has an alpha of 0 we can't use SDL_BLENDMODE_ADD since we not only would end up with incorrect RGB colors (same multiplication as above) but an alpha of 0, so we need to write a custom blend operation.
由于你的图像的alpha值为0,我们不能使用SDL_BLENDMODE_ADD,因为我们不仅会得到不正确的RGB颜色(与上面相同的乘法),而且alpha值为0,所以我们需要编写一个自定义混合操作。
I've come with this that I think emulates the final render to the window
我带来了这个,我认为模仿窗口的最终渲染
SDL_BlendMode mode = SDL_ComposeCustomBlendMode(1, 1, SDL_BLENDOPERATION_ADD,
1, 1, SDL_BLENDOPERATION_ADD);
SDL_SetTextureBlendMode(texture, mode);
This does the following (Check SDL_ComposeCustomBlendMode)
这将执行以下操作(检查SDL_ComposeCustomBlendMode)
dstRGB = srcRGB + dstRGB
dstA = srcA + dstA
I've come to this result by checking the pixel values of an image that I saved from the window where I interleaved the texture twice.
我通过检查从窗口保存的图像的像素值来得到这个结果,在那里我将纹理交错了两次。
Here is an image of the result (I didn't have your background picture)
这是结果的图像(我没有你的背景图片)
And your code modified (it didn't compile and I added an Event loop so it doesn't freezes)
并修改了你的代码(它没有编译,我添加了一个Event循环,因此它不会冻结)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *background = NULL;
SDL_Texture *image = NULL;
SDL_Surface* surface = NULL;
SDL_Rect dst = {20, 20, 100, 100};
#define SOLID 255
#define HALF_TRANSPARENT 128
#define TRANSPARENT 0
SDL_Texture * renderImageInATexture(SDL_Texture *image, Uint8 transparency) {
// Create an intermediate texture
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 200, 200);
SDL_BlendMode mode = SDL_ComposeCustomBlendMode(1, 1, SDL_BLENDOPERATION_ADD,
1, 1, SDL_BLENDOPERATION_ADD);
SDL_SetTextureBlendMode(texture, mode);
SDL_SetRenderTarget(renderer, texture);
// Paint it
SDL_SetRenderDrawColor(renderer, (Uint8) 0, (Uint8) 0, (Uint8) 0,(Uint8) transparency);
SDL_RenderClear(renderer);
// Render the image in intermediate texture
SDL_SetRenderTarget(renderer, texture);
SDL_RenderCopy( renderer, image, NULL, &dst );
dst.x = 100;
// Check what happens if we render 2 images overlapped into the target (compare against the window).
SDL_RenderCopy( renderer, image, NULL, &dst );
SDL_SetRenderTarget(renderer, NULL);
dst.x = 20;
return texture;
}
int main(int argc, char *argv[]) {
SDL_Init( SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);
window = SDL_CreateWindow("Win", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 200, SDL_WINDOW_SHOWN);
// So we can save into pngs to check the value
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
int quit = 0;
while (!quit) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
quit = 1;
}
}
SDL_RenderClear(renderer);
SDL_SetRenderTarget(renderer, NULL);
surface = IMG_Load( "./carbone.png" );
image = SDL_CreateTextureFromSurface( renderer, surface );
SDL_FreeSurface( surface );
// draw imagein main display
SDL_RenderCopy(renderer, image, NULL, &dst );
SDL_Rect dstTexture = {100, 20, 100, 100};
SDL_RenderCopy(renderer, image, NULL, &dstTexture);
SDL_Texture *texture3 = renderImageInATexture(image, TRANSPARENT);
// render intermediate texture3 in main display
SDL_Rect dstTexture3 = {600, 0, 200, 200};
SDL_RenderCopy( renderer, texture3, NULL, &dstTexture3 );
SDL_RenderPresent(renderer);
// Uncomment below if you want to save the final result into an image to check pixel values.
/*
SDL_Surface *surf = SDL_GetWindowSurface(window);
printf("%s\n", SDL_GetError());
printf("%d\n", surf);
SDL_LockSurface(surf);
IMG_SavePNG(surf, "img6.png");
SDL_UnlockSurface(surf);
exit(0);*/
SDL_Delay(33);
}
}
#1
0
All right, the thing is that blendmode_blend works like
好吧,问题是blendmode_blend就像
dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
dstA = srcA + (dstA * (1-srcA))
Now since your alpha is less than < 1 (1 being 255 in SDL scale) your dstRGB gets attenuated (ie lower values). For example using your circle's border you get that RGBA all are equal to 38, scaling this is alpha ~ 0.149, which gives and end result of RGBA ~ 5 (38 * 0.149).
现在,由于您的alpha小于<1(SDL标度为1),因此dstRGB会衰减(即较低的值)。例如,使用你的圆圈边界,你得到的RGBA都等于38,缩放这是alpha~0.149,这给出了RGBA~5(38 * 0.149)的最终结果。
Since your image also has an alpha of 0 we can't use SDL_BLENDMODE_ADD since we not only would end up with incorrect RGB colors (same multiplication as above) but an alpha of 0, so we need to write a custom blend operation.
由于你的图像的alpha值为0,我们不能使用SDL_BLENDMODE_ADD,因为我们不仅会得到不正确的RGB颜色(与上面相同的乘法),而且alpha值为0,所以我们需要编写一个自定义混合操作。
I've come with this that I think emulates the final render to the window
我带来了这个,我认为模仿窗口的最终渲染
SDL_BlendMode mode = SDL_ComposeCustomBlendMode(1, 1, SDL_BLENDOPERATION_ADD,
1, 1, SDL_BLENDOPERATION_ADD);
SDL_SetTextureBlendMode(texture, mode);
This does the following (Check SDL_ComposeCustomBlendMode)
这将执行以下操作(检查SDL_ComposeCustomBlendMode)
dstRGB = srcRGB + dstRGB
dstA = srcA + dstA
I've come to this result by checking the pixel values of an image that I saved from the window where I interleaved the texture twice.
我通过检查从窗口保存的图像的像素值来得到这个结果,在那里我将纹理交错了两次。
Here is an image of the result (I didn't have your background picture)
这是结果的图像(我没有你的背景图片)
And your code modified (it didn't compile and I added an Event loop so it doesn't freezes)
并修改了你的代码(它没有编译,我添加了一个Event循环,因此它不会冻结)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *background = NULL;
SDL_Texture *image = NULL;
SDL_Surface* surface = NULL;
SDL_Rect dst = {20, 20, 100, 100};
#define SOLID 255
#define HALF_TRANSPARENT 128
#define TRANSPARENT 0
SDL_Texture * renderImageInATexture(SDL_Texture *image, Uint8 transparency) {
// Create an intermediate texture
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 200, 200);
SDL_BlendMode mode = SDL_ComposeCustomBlendMode(1, 1, SDL_BLENDOPERATION_ADD,
1, 1, SDL_BLENDOPERATION_ADD);
SDL_SetTextureBlendMode(texture, mode);
SDL_SetRenderTarget(renderer, texture);
// Paint it
SDL_SetRenderDrawColor(renderer, (Uint8) 0, (Uint8) 0, (Uint8) 0,(Uint8) transparency);
SDL_RenderClear(renderer);
// Render the image in intermediate texture
SDL_SetRenderTarget(renderer, texture);
SDL_RenderCopy( renderer, image, NULL, &dst );
dst.x = 100;
// Check what happens if we render 2 images overlapped into the target (compare against the window).
SDL_RenderCopy( renderer, image, NULL, &dst );
SDL_SetRenderTarget(renderer, NULL);
dst.x = 20;
return texture;
}
int main(int argc, char *argv[]) {
SDL_Init( SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);
window = SDL_CreateWindow("Win", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 200, SDL_WINDOW_SHOWN);
// So we can save into pngs to check the value
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
int quit = 0;
while (!quit) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
quit = 1;
}
}
SDL_RenderClear(renderer);
SDL_SetRenderTarget(renderer, NULL);
surface = IMG_Load( "./carbone.png" );
image = SDL_CreateTextureFromSurface( renderer, surface );
SDL_FreeSurface( surface );
// draw imagein main display
SDL_RenderCopy(renderer, image, NULL, &dst );
SDL_Rect dstTexture = {100, 20, 100, 100};
SDL_RenderCopy(renderer, image, NULL, &dstTexture);
SDL_Texture *texture3 = renderImageInATexture(image, TRANSPARENT);
// render intermediate texture3 in main display
SDL_Rect dstTexture3 = {600, 0, 200, 200};
SDL_RenderCopy( renderer, texture3, NULL, &dstTexture3 );
SDL_RenderPresent(renderer);
// Uncomment below if you want to save the final result into an image to check pixel values.
/*
SDL_Surface *surf = SDL_GetWindowSurface(window);
printf("%s\n", SDL_GetError());
printf("%d\n", surf);
SDL_LockSurface(surf);
IMG_SavePNG(surf, "img6.png");
SDL_UnlockSurface(surf);
exit(0);*/
SDL_Delay(33);
}
}