How can I change opacity of the SDL_Texture? And I don't know how to apply the opacity number in my function.
My code
void drawTexture(SDL_Texture *img, int x, int y, int width, int height, double opacity)
{
SDL_Rect SrcR;
SDL_Rect DestR;
SrcR.x = 0;
SrcR.y = 0;
SrcR.w = width;
SrcR.h = height;
DestR.x = x;
DestR.y = y;
DestR.w = width;
DestR.h = height;
SDL_RenderCopy(_main::_main_renderer, img, &SrcR, &DestR);
}
Use SDL_SetTextureAlphaMod:
SDL_SetTextureAlphaMod(img, opacity);
This will set the opacity (alpha) of the texture, the alpha value must be a Uint8 from 0 (totally transparent aka invisible) to 255 (fully opaque).
Opening question doesn't have info on the origin of img texture so the chosen answer it not correct it the texture is created from raw pixel data that doesn't have Alpha, i.e. using this:
SDL_UpdateTexture(img, NULL, pixels, pitch);
If pixels contains raw pixel data without alpha, i.e. ARGB with A 0x00, even if you do this
SDL_UpdateTexture(img, NULL, pixels, pitch);
SDL_SetTextureAlphaMod(img, opacity);
you will not see the texture (in this case alpha is 0x00) or you'll see garbage
Related
I am looking for a function that draws a filled circle using SDL2 without using a renderer at all. I currently have this:
void Circle(int center_x, int center_y, int radius, SDL_Color color) {
eraseOldCircle();
uint32_t *pixels = (uint32_t *) windowSurface->pixels;
SDL_PixelFormat *windowFormat = windowSurface->format;
SDL_LockSurface(windowSurface); // Lock surface for direct pixel access capability
int radiussqrd = radius * radius;
for(int x=center_x-radius; x<=center_x+radius; x++) {
int dx = center_x - x;
for(int y=center_y-radius; y<=center_y+radius; y++) {
int dy = center_y - y;
if((dy * dy + dx * dx) <= radiussqrd) {
pixels[(y * WIDTH + x)] = SDL_MapRGB(windowFormat, color.r, color.g, color.b);
}
}
}
SDL_UnlockSurface(windowSurface);
SDL_UpdateWindowSurface(window);
}
which has been adapted from another function I found here, it draws the pixels directly to the windowSurface after calling eraseOldCircle (which puts the game's background image back to the previous position of the circle, effectively erasing it from there.) but it is still too slow for what I need (probably the maths?). What would be the fastest way to draw a circle using direct pixel access? I need it to be high speed so I can use it in a 2D game. I haven't been able to find anything until now, everything I see uses SDL_Renderer, but I should strictly never use it.
Here is eraseOldCircle() in case it helps:
void eraseOldCircle() {
//Calculate previous position of ball
SDL_Rect pos = {circlePosition.x-(radius+steps), circlePosition.y-(radius+steps), radius*radius, radius*2+steps};
SDL_BlitSurface(backgroundImage, &pos, windowSurface, &pos);
}
I'm not too sure how to do it with surfaces and memory management and all that, but if this helps, here is a version using an SDL_Renderer that runs pretty quickly:
void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, SDL_Color color)
{
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
for (int w = 0; w < radius * 2; w++)
{
for (int h = 0; h < radius * 2; h++)
{
int dx = radius - w; // horizontal offset
int dy = radius - h; // vertical offset
if ((dx*dx + dy*dy) <= (radius * radius))
{
SDL_RenderDrawPoint(renderer, x + dx, y + dy);
}
}
}
}
If you draw many circles, I would guess SDL_UpdateWindowSurface is where you spend the most time. Try this instead
SDL_LockSurface
// erase and draw all circles (possibly >1000)
SDL_UnlockSurface
SDL_UpdateWindowSurface
You can optimize your circle drawing code a bit, but it is probably fast enough. I also think that SDL_Renderer is probably fast enough.
The documentation for SDL_UpdateWindowSurface says it will copy the surface to the screen. You only need to do this once per frame.
Is there a method in Qt with which one can easily create a picture based on data stored in a std::vector? I mean that in the vector there are colors for each QPointF points of a QWidget on which I'm painting with QPainter, but I don't only need to draw this picture on the QWidget using the colors in the vector, but to save it as a picture too.
If you know the initial dimensions of your image and have a vector with the color information, you can do the following:
// Image dimensions.
const int width = 2;
const int height = 2;
// Color information: red, green, blue, black pixels
unsigned int colorArray[width * height] =
{qRgb(255, 0, 0), qRgb(0, 255, 0), qRgb(0, 0, 255), qRgb(0, 0, 0)};
// Initialize the vector
std::vector<unsigned int> colors(colorArray, colorArray + width * height);
// Create new image with the same dimensions.
QImage img(width, height, QImage::Format_ARGB32);
// Set the pixel colors from the vector.
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
img.setPixel(row, col, colors[row * width + col]);
}
}
// Save the resulting image.
img.save("test.png");
I have to draw a conical gradient in Qt C++ but I can not use the QConicalGradient. I did have a linear gradient, but I do not know how to make a conical gradient. I do not want the finished code, but I ask for a simple algorithm.
for(int y = 0; y < image.height(); y++){
QRgb *line = (QRgb *)image.scanLine(y);
for(int x = 0; x < image.width(); x++){
QPoint currentPoint(x, y);
QPoint relativeToCenter = currentPoint - centerPoint;
float angle = atan2(relativeToCenter.y(), relativeToCenter.x);
// I have a problem in this line because I don't know how to set a color:
float hue = map(-M_PI, angle, M_PI, 0, 255);
line[x] = (red << 16) + (grn << 8) + blue;
}
}
Can you help me?
Here is some pseudo code:
Given some area to paint on, and a defined center for your gradient...
For each point that you are painting on in the area, calculate the angle to the center of your gradient.
// QPoint currentPoint; // created/populated with a x, y value by two for loops
QPoint relativeToCenter = currentPoint - centerPoint;
angle = atan2(relativeToCenter.y(), relativeToCenter.x());
Then map that angle to a color using your linear gradient, or some sort of mapping function.
float hue = map(-PI, angle, PI, 0, 255); // convert angle in radians to value
// between 0 and 255
Paint that pixel, and repeat for every pixel in your area.
EDIT: Depending on the pattern of the gradient, you will want to create a different QColor pixel. For example if you had a "rainbow" gradient, just going from one hue to the next, you could use a linear mapping function like this:
float map(float x1, float x, float x2, float y1, float y2)
{
if(true){
if(x<x1)
x = x1;
if(x>x2)
x = x2;
}
return y1 + (y2-y1)/(x2-x1)*(x-x1);
}
Then you create a QColor object using the outputted value:
float hue = map(-PI, angle, PI, 0, 255); // convert angle in radians to value
// between 0 and 255
QColor c;
c.setHsl( (int) hue, 255, 255);
Then use this QColor object with your QPainter or QBrush or QPen that you are using. Or if you are putting a qRgb value back in:
line[x] = c.rgb();
http://qt-project.org/doc/qt-4.8/qcolor.html
Hope that helps.
I have a slight problem: I can't modify the pixels of an SDL screen.
Specifically, the following code doesn't work.
Uint32 * pixels = (Uint32 *) screen -> pixels;
screen -> pixels = pixels;
This compiles, but it doesn't show anything. What am I missing?
I had the following functions lying around for setting pixels in an SDL_Surface. There are two versions each for 32-bit, 24-bit, 16-bit and 8-bit surfaces. If you just want to set a single pixel, you would use the normal versions. But if you want to set a bunch of pixels, first you lock the surface, then you use the nolock version(named so because it does not lock the surface), then you unlock. This way you aren't repeatedly locking and unlocking the surface, which is supposed to be an expensive operation, though I don't think I ever actually tested it.
void PutPixel32_nolock(SDL_Surface * surface, int x, int y, Uint32 color)
{
Uint8 * pixel = (Uint8*)surface->pixels;
pixel += (y * surface->pitch) + (x * sizeof(Uint32));
*((Uint32*)pixel) = color;
}
void PutPixel24_nolock(SDL_Surface * surface, int x, int y, Uint32 color)
{
Uint8 * pixel = (Uint8*)surface->pixels;
pixel += (y * surface->pitch) + (x * sizeof(Uint8) * 3);
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
pixel[0] = (color >> 24) & 0xFF;
pixel[1] = (color >> 16) & 0xFF;
pixel[2] = (color >> 8) & 0xFF;
#else
pixel[0] = color & 0xFF;
pixel[1] = (color >> 8) & 0xFF;
pixel[2] = (color >> 16) & 0xFF;
#endif
}
void PutPixel16_nolock(SDL_Surface * surface, int x, int y, Uint32 color)
{
Uint8 * pixel = (Uint8*)surface->pixels;
pixel += (y * surface->pitch) + (x * sizeof(Uint16));
*((Uint16*)pixel) = color & 0xFFFF;
}
void PutPixel8_nolock(SDL_Surface * surface, int x, int y, Uint32 color)
{
Uint8 * pixel = (Uint8*)surface->pixels;
pixel += (y * surface->pitch) + (x * sizeof(Uint8));
*pixel = color & 0xFF;
}
void PutPixel32(SDL_Surface * surface, int x, int y, Uint32 color)
{
if( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
PutPixel32_nolock(surface, x, y, color);
if( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
void PutPixel24(SDL_Surface * surface, int x, int y, Uint32 color)
{
if( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
PutPixel24_nolock(surface, x, y, color);
if( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
}
void PutPixel16(SDL_Surface * surface, int x, int y, Uint32 color)
{
if( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
PutPixel16_nolock(surface, x, y, color);
if( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
void PutPixel8(SDL_Surface * surface, int x, int y, Uint32 color)
{
if( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
PutPixel8_nolock(surface, x, y, color);
if( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
Manipulating the contents of screen->pixels will modify pixels, with a couple of caveats.
First, as you've shown in the code snippet, note that screen->pixels is a pointer to the pixel data of the surface. The pixel data itself is accessed as a linear array from that pointer based on the width of the surface (surface->pitch) and the size of the pixel in bytes.
The pixel size (aka depth) is set during initialisation, using SDL_SetVideoMode() and can be found in screen->format->BytesPerPixel.
Locking of the surface before making changes may be necessary.
In addition, depending on the options that were passed to SDL_SetVideoMode() you may also need to call SDL_Flip() to display the changes that you've made.
A working example of pixel manipulation can be found here.
As has been pointed out in the comments, the code listed in the question is not actually going to do anything visible as no changes are being made to the pixel data.
Adding an SDL2 variant manipulating pixels not on a surface but in a renderer (and which does not crash if you try to manipulate pixels outside of your screen, unlike previous answers)
void putPixelRGB(SDL_Renderer* renderer, int x, int y, unsigned char r, unsigned char g, unsigned char b)
{
SDL_SetRenderDrawColor(renderer, (Uint8)r, (Uint8)g, (Uint8)b, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
You must not modify the contents of the SDL_Surface structure. If you want to copy the pixels you should malloc() some memory and then memcpy() the pixels.
why change a pixel?
make a new surface & a Rect
// CODE ------------>
SDL_Surface *screen, *PIXEL = NULL;
SDL_Rect PIXELRect;
PIXELRect.h=5;
PIXELRect.w=5;
PIXELRect.x=100;
PIXELRect.y=100;
screen = SDL_SetVideoMode(640, 468, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_ANYFORMAT);
while(running){
SDL_FillRect(screen, &PIXELRect, (#color));
}
I want to render an anti-aliased string on an SDL_Surface with a given alpha channel.
I figured out it is possible to render:
an anti-aliased string with the Blended variant of the string render method (ie: TTR_RenderText_Blended). But then I can't make it transparent.
An anti-aliased string with the Shaded method. But then there is a solid background. The background and the drawn string can be made transparent, but then the solid background is still there. Passing it a transparent background color is also not possible.
an non-anti-aliased string, which I can make transparent like I want with the Solid variant. But it is not anti-aliased.
Thanks
I know I'm a bit late on this one :/
According to SDL documentation on SDL_SetAlpha:
Note that per-pixel and per-surface alpha cannot be combined; the per-pixel alpha is always used if available.
So regular SDL_BlitSurface/SDL_SetAlpha won't work here. But it can be done:
The only ways I can think of alpha-blending TTF_RenderText_Blended output are to use OpenGL, or adjust the alpha values of each pixel in the surface.
By adjusting per-pixel alpha values
You can do this by scaling the per-pixel alpha values from [0, 255] to a new range [0, alpha]:
// Changes a surface's alpha value, by altering per-pixel alpha if necessary.
void SetSurfaceAlpha (SDL_Surface *surface, Uint8 alpha)
{
SDL_PixelFormat* fmt = surface->format;
// If surface has no alpha channel, just set the surface alpha.
if( fmt->Amask == 0 ) {
SDL_SetAlpha( surface, SDL_SRCALPHA, alpha );
}
// Else change the alpha of each pixel.
else {
unsigned bpp = fmt->BytesPerPixel;
// Scaling factor to clamp alpha to [0, alpha].
float scale = alpha / 255.0f;
SDL_LockSurface(surface);
for (int y = 0; y < surface->h; ++y)
for (int x = 0; x < surface->w; ++x) {
// Get a pointer to the current pixel.
Uint32* pixel_ptr = (Uint32 *)(
(Uint8 *)surface->pixels
+ y * surface->pitch
+ x * bpp
);
// Get the old pixel components.
Uint8 r, g, b, a;
SDL_GetRGBA( *pixel_ptr, fmt, &r, &g, &b, &a );
// Set the pixel with the new alpha.
*pixel_ptr = SDL_MapRGBA( fmt, r, g, b, scale * a );
}
SDL_UnlockSurface(surface);
}
}
I know it looks scary, but it's pretty straight-forward. The key line is here:
*pixel_ptr = SDL_MapRGBA( fmt, r, g, b, scale * a );
You can use it like this:
text_surface = TTF_RenderText_Blended( font, "Hello World!", color );
SetSurfaceAlpha( text_surface, 128 );
With OpenGL
If using OpenGL, things are lot easier. Assuming you convert the SDL_Surface from TTF_RenderText_Blended to a GL texture, you can just use:
glColor4f( 1.0, 1.0, 1.0, Alpha );
before you render it on a textured quad.
But don't forget to enable alpha blending first!
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
All you have to do is call SDL_SetAlpha on your SDL_Surface after creating the sprite on which the text is rendered, but before calling SDL_DisplayFormatAlpha on that sprite.
// Make the sprite with the text on it
SDL_Surface *swSprite = TTF_RenderText_Solid( font, text, textColor ) ;
// CALL SET ALPHA NOW
SDL_SetAlpha( swSprite, SDL_SRCALPHA, 128 ) ; // 50% opacity
// OK, NOW you can convert it to display format. I'm presuming
// you called `SDL_SetVideoMode` with the `SDL_HWSURFACE` flag set previously
SDL_Surface* hwSprite = SDL_DisplayFormatAlpha( swSprite ) ;
// If you invert the above 2 steps, it won't work.
// We don't need the software sprite anymore
SDL_FreeSurface( swSprite ) ;
// Now draw the hwSprite as normal
SDL_BlitSurface( hwSprite, NULL, screen, &spriteLocation );
The way to do this with a TTF is to use SDL_SetTextureAlphaMod() Something like this
SDL_Surface *surface;
SDL_Texture *texture;
SDL_Color color = {255, 255, 255, 128}
surface = TTF_RenderText_Blended_Wrapped(myFont, "HI!", color, 100);
// myFont and the _renderer is pre-defined somewhere in the game
texture = SDL_CreateTextureFromSurface(_renderer, surface);
SDL_SetTextureAlphaMod(texture, color.a);
SDL_RenderCopy(_renderer, texture, NULL, &dest);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
/*...*/
SDL_RenderPresent(_renderer);
https://wiki.libsdl.org/SDL_SetTextureAlphaMod
Why not use bitmapped fonts? You could build a png image with alpha channel. I think that SDL_ttf works with the same system, it builds an image an internally uses bitmapped fonts.