I'm trying to make a simple image viewer. I basically load an image into a surface and then create a texture from it.
At the end, I do the usual SDL_RenderClear(), SDL_RenderCopy() and SDL_RenderPresent() as per the migration guide.
This works fine, except that if I call SDL_UpdateTexture() before the 3 render calls above, I get a messed up image:
I am calling SDL_UpdateTexture() like this:
SDL_UpdateTexture(texture, NULL, image->pixels, image->pitch)
Where image is the surface I loaded for the image and texture is the texture I created from that. Attempts to vary the pitch result in differently messed up images. I also tried using a rect for the second parameter, but results are the same if the rect has the same dimensions as the image. If the dimensions are larger (e.g. same as the window), the update doesn't happen, but there are no errors.
The full code is available.
I would like to manipulate pixels of the surface directly via image->pixels and then call SDL_UpdateTexture(), but just calling SDL_UpdateTexture() without any tampering is enough to mess things up.
I think there is something wrong with the pitch or the SDL_Rect parameters,
but there is another SDL function which might help:
SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer,
SDL_Surface* surface)
Could you maybe try the following. It should replace any pink (r=255,g=0,b=255) pixels to be transparent. You would simply change the pixel32 manipulation to accommodate your needs.
SDL_Surface* image = IMG_Load(filename);
SDL_Surface* imageFomatted = SDL_ConvertSurfaceFormat(image,
SDL_PIXELFORMAT_RGBA8888,
NULL);
texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
imageFomatted->w, imageFomatted->h);
void* pixels = NULL;
int pitch = 0;
SDL_LockTexture(texture, &imageFomatted->clip_rect, &pixels, &pitch);
memcpy(pixels, imageFomatted->pixels, (imageFomatted->pitch * imageFomatted->h));
int width = imageFomatted->w;
int height = imageFomatted->h;
Uint32* pixels32 = (Uint32*)pixels;
int pixelCount = (pitch / 4) * height;
Uint32 colorKey = SDL_MapRGB(imageFomatted->format, 0xFF, 0x00, 0xFF);
Uint32 transparent = SDL_MapRGBA(imageFomatted->format, 0xFF, 0x00, 0xFF, 0x00);
for (int i = 0; i < pixelCount; i++) {
if (pixels32[i] == colorKey) {
pixels32[i] = transparent;
}
}
SDL_UnlockTexture(texture);
SDL_FreeSurface(imageFormatted);
SDL_FreeSurface(image);
pixels = NULL;
pitch = 0;
width = 0;
height = 0;
Related
I want to rotate a sprite in C and SDL2, with a set center of rotation, and without scaling or anti-aliasing.
My game resolution is 320x240, and the display is scaled up when I set the game to full screen, because I'm using SDL_RenderSetLogicalSize(renderer, 320, 240).
Using SDL2's SDL_RenderCopyEx() (or SDL_RenderCopyExF()) to rotate a SDL_Texture.
As shown in this example ( https://imgur.com/UGNDfEY ) when the window is set to full screen, the texture is scaled up and at much higher resolution. Is would like the final 320x240 rendering to be scaled up, not the individual textures.
Using SDL_gfx's rotozoomSurface() was a possible alternative.
However, as shown in this example ( https://imgur.com/czPEUhv ), while this method give the intended low-resolution and aliased look, it has no center of rotation, and renders the transparency color as half-transparent black.
Is there a function that does what I'm looking for? Are there some tricks to get around that?
What I would do is to render what you want to in a SDL_Texture, and then print this texture into the renderer, using something like :
// Set 'your_texture' as target
SDL_SetRenderTarget(your_renderer, your_texture);
// We are now printing the rotated image on the texture
SDL_RenderCopyEx(your_renderer, // we still use the renderer; it will be automatically printed into the texture 'your_texture'
your_image,
&srcrect,
&dstrect,
angle,
¢er,
SDL_FLIP_NONE); // unless you want to flip vertically / horizontally
// Set the renderer as target and print the previous texture
SDL_SetRenderTarget(your_renderer, NULL);
SDL_RenderClear(your_renderer);
SDL_RenderCopy (your_renderer, your_texture, NULL, NULL); // here the scale is automatically done
SDL_RenderPresent(your_renderer);
It works, but I don't know if it is very efficient.
Don't forget to define your_texture with a SDL_TEXTUREACCESS_TARGET access.
Hope this helps,
Durza42
Thanks to #Durza42, here's the solution to my problem:
#define kScreenWidth 320
#define kScreenHeight 240
SDL_Window* g_window = NULL;
SDL_Texture* g_texture = NULL;
SDL_Renderer* g_renderer = NULL;
SDL_Texture* g_sprite = NULL;
double g_sprite_angle = 0.0;
SDL_FRect g_sprite_frect = {
.x = 50.0f,
.y = 50.0f,
.w = 32.0f,
.h = 32.0f,
};
void
sdl_load(void)
{
SDL_Init(SDL_INIT_VIDEO);
g_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, kScreenWidth, kScreenHeight, 0);
g_renderer = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_PRESENTVSYNC);
g_texture = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, kScreenWidth, kScreenHeight);
SDL_RenderSetLogicalSize(g_renderer, kScreenWidth, kScreenHeight);
}
void
sprite_load(void)
{
g_sprite = IMG_LoadTexture(g_renderer, "./sprite.png");
}
void
draw(void)
{
SDL_SetRenderTarget(g_renderer, g_texture);
SDL_RenderClear(g_renderer);
SDL_RenderCopyExF(g_renderer, g_sprite, NULL, &g_sprite_frect, g_sprite_angle, NULL, SDL_FLIP_NONE);
SDL_SetRenderTarget(g_renderer, NULL);
SDL_RenderClear(g_renderer);
SDL_RenderCopy(g_renderer, g_texture, NULL, NULL);
SDL_RenderPresent(g_renderer);
}
I have found an application (ufsm/ufsm-compose), which uses Cairo internally, to allow for vector drawing in the application GUI canvas.
I'd like to try to export the canvas drawing as a vector image - primarily SVG - with minimal changes to the program, however, I'm not sure whether it is possible.
This application uses gtk_drawing_area_new to instantiate a GtkWidget (ufsmm_canvas_new in ufsm-compose/controller.c), and then a draw_cb callback is made to run on draw event - similar to the approach here:
https://zetcode.com/gfx/cairo/basicdrawing/
Then, draw_cb "automagically" receives a reference to cairo_t, and uses that in calls to rendering functions, that use typical cairo_rectangle etc draw commands (see ufsmm_canvas_render in ufsm-compose/render.c).
However, I'm not really sure whether I can export these drawings somehow in a vector image (SVG). For instance, on this page:
https://www.cairographics.org/manual/cairo-SVG-Surfaces.html
... I can see that for SVG, one should call cairo_svg_surface_create - however, the ufsm-compose application does not use this command (in fact, there is no mention of the word "surface" anywhere in the ufsm-compose code -- which, otherwise, figures also in say cairo_image_surface_create (https://www.cairographics.org/tutorial/) which is used for bitmap images).
So, what are my options in exporting this drawing as an SVG (or other vector format)? Could I get away with instantiating a cairo_svg_surface_create upon export command, then somehow copying the application canvas' cairo_t to this SVG, and then finally save the SVG? If so - how exactly do I do this - can a full example be found on the Internet?
Could I get away with instantiating a cairo_svg_surface_create upon export command, then somehow copying the application canvas' cairo_t to this SVG, and then finally save the SVG? If so - how exactly do I do this - can a full example be found on the Internet?
Looking at the code of the draw_cb, one finds:
struct ufsmm_canvas *priv =
g_object_get_data(G_OBJECT(widget), "canvas private");
gint width, height;
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
width = allocation.width;
height = allocation.height;
priv->cr = cr;
//priv->menu->cr = cr;
priv->window_width = width;
priv->window_height = height;
ufsmm_canvas_render(priv, width, height);
ufsmm_nav_render(priv, width, height);
//menu_render(priv->menu, priv->theme, priv->selection, width, height);
uc_status_render(priv, width, height);
So, apparently, the state of the application is kept in a struct ufsmm_canvas. When you have such a canvas, you have to decide on a size of your drawing and then there are just three functions to call to do the drawing.
So, to export the drawing, one could do (completely untested):
void export_drawing(struct ufsmm_canvas *priv, int width, int height, const char* filename) {
cairo_surface_t *surface = cairo_svg_surface_create(filename, width, height);
cairo_t *cr = cairo_create(surface);
priv->cr = cr;
priv->window_width = width;
priv->window_height = height;
ufsmm_canvas_render(priv, width, height);
ufsmm_nav_render(priv, width, height);
//menu_render(priv->menu, priv->theme, priv->selection, width, height);
uc_status_render(priv, width, height);
cairo_destroy(cr);
cairo_destroy(surface);
}
EDIT: here is a tested version, along with a quick-n-dirty hack, so svg image always gets exported upon a save command:
#include <cairo-svg.h>
...
void export_drawing(struct ufsmm_canvas *priv, int width, int height) {
printf("export_drawing ufsm_out.svg ...\n");
cairo_surface_t *surface = cairo_svg_surface_create("ufsm_out.svg", width, height);
cairo_t *cr = cairo_create(surface);
cairo_t *old_cr = priv->cr;
priv->cr = cr;
ufsmm_canvas_render(priv, width, height);
cairo_destroy(cr);
cairo_surface_destroy(surface);
priv->cr = old_cr;
printf("export_drawing DONE\n");
}
void canvas_save(void *context)
{
struct ufsmm_canvas *priv = (struct ufsmm_canvas *) context;
if (priv->model->filename == NULL) {
canvas_save_as(context);
} else {
L_DEBUG("%s: writing to '%s'", __func__, priv->model->filename);
remove_dangling_guard_refs(priv);
ufsmm_model_write(priv->model->filename, priv->model);
uc_rstatus_set(false);
}
export_drawing(priv, priv->window_width, priv->window_height);
}
I'm not able to get the alpha channel to work properly in SDL using a pixel map. Essentially it seems to get fairly transparent at low values, but as the alpha values approach 0, the pixels become black.
I've tried setting the blend mode for both the texture and the renderer, and searched around for other possible solutions. So far the documentation hasn't helped me figure out a solution. So here is a minimal example I made:
#include "SDL.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
unsigned int* PixelMap;
SDL_Window* Window;
SDL_Renderer* Renderer;
SDL_Texture* Texture;
int
SDL_main(int argc, char* args[])
{
PixelMap = (unsigned int*)malloc(SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(*PixelMap));
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN, &Window, &Renderer);
Texture = SDL_CreateTexture(Renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
SDL_SetTextureBlendMode(Texture, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawBlendMode(Renderer, SDL_BLENDMODE_BLEND);
while(1)
{
for(int I = 0; I < SCREEN_WIDTH * SCREEN_HEIGHT; ++I)
{
PixelMap[I] = 0xFFFCCFFF;
}
for(int I = SCREEN_WIDTH/2 - 100; I < SCREEN_WIDTH/2 + 100; ++I)
{
PixelMap[I + (SCREEN_WIDTH * SCREEN_HEIGHT/2)] = 0x00000000;
}
SDL_UpdateTexture(Texture, 0, PixelMap, SCREEN_WIDTH * sizeof(*PixelMap));
SDL_RenderClear(Renderer);
SDL_RenderCopy(Renderer, Texture, 0, 0);
SDL_RenderPresent(Renderer);
}
}
This isn't the exact code I'm using, but it seems to produce either the same or a similar issue. Here I would expect the line drawn to be transparent since I'm assigning 0 to the location, the blend mode has been set, and the pixel format is set to RGBA8888. But I'm seeing a black line instead.
What am I missing?
Thanks for reading!
EDIT: Oh, duh! I'm overwriting the data, so it has nothing to blend with-- I guess the default background color is black, so it's blending with that. So the solution would be to render the background first, then add the changed pixels on top of that.
I'll leave this up in case it helps anyone else in the future.
I am trying to create an HBITMAP from an array which will contain the color values for the Pixels. The thing is when I try to create a 24-bpp Bitmap, the CreateDIBItmap is using BGR values instead of RGB as I would like.
The code to create the Bitmap is as follows:
image_size = 600 * 600 * 3;
aimp_buffer = (char *)malloc(image_size * sizeof(char));
for (counter = 0; counter < image_size;)
{
aimp_buffer[counter++] = 255;
aimp_buffer[counter++] = 0;
aimp_buffer[counter++] = 0;
}
ads_scrbuf->avo_buffer = (void *)aimp_buffer;
ads_scrbuf->im_height = 600;
ads_scrbuf->im_width = 600;
ads_scrbuf->im_scanline = 600;
memset(&info, 0, sizeof(info));
memset(&info.bmiHeader, 0, sizeof(info.bmiHeader));
info.bmiHeader.biBitCount = 24;
info.bmiHeader.biHeight= -600;
info.bmiHeader.biWidth= 600;
info.bmiHeader.biSize = sizeof(info.bmiHeader);
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biCompression = BI_RGB;
memset(&header, 0, sizeof(BITMAPV5HEADER));
header.bV5Width = 600;
header.bV5Height = 600;
header.bV5BitCount = 24;
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Planes = 1;
header.bV5Compression = BI_RGB;
*adsp_hBitmap = CreateDIBitmap(GetDC(ds_apiwindow), (BITMAPINFOHEADER *)&header,
CBM_INIT, (void *)ads_scrbuf->avo_buffer, &info, DIB_RGB_COLORS)
This should create a Red background for all of the image, but instead it is blue.
The Windows convention for DIB bitmaps is BGR. You can't change that. You will simply have to adapt to it.
If you load for instance *.bmp file to memory or rather you make a variable lets say DWORD cRef = 0xFF0000 and fill a memory with it, in second case you will see RED color, so the byte order is BGR in both cases (seen as 0xRRGGBB value in source code editor for mentioned variable). But! Try to call e.g. SetTextColor(hDc, cRef) or so. The very same value will be BLUE, so it will be a hell of an adaptation, because Windows convention for DIB bitmaps is just the opposite of Windows convention for e.g. HBRUSH objects. I'd really wonder in which way is this useful..
I'm new to OpenCv and have been using it for a small project.
I intend to fill a single channel image all over, except a rectangle region within the image.
I have two problems.
1) Filling a single channel image with black. (cvSet wont work on single channel)
2) Carrying out the fill all over the image except a rectangle region within the image.
Any solutions?
Here's a program that shows how to fill a single channel with black and also how to set the image to black with a mask.
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
int main(int argc, const char * argv[]) {
cv::Mat image;
image = cv::imread("../../lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
if (!image.data) {
std::cout << "Image file not found\n";
return 1;
}
cv::namedWindow("original");
cv::imshow("original", image);
//Define the ROI rectangle
cv::Rect ROIrect(100, 100, 200, 200);
//Create a deep copy of the image
cv::Mat fill(image.clone());
//Specify the ROI
cv::Mat fillROI = fill(ROIrect);
//Fill the ROI with black
fillROI = cv::Scalar(0);
cv::namedWindow("fill");
cv::imshow("fill", fill);
cvMoveWindow("fill", 500, 40);
//create a deep copy of the image
cv::Mat inverseFill(image.clone());
//create a single-channel mask the same size as the image filled with 1
cv::Mat inverseMask(inverseFill.size(), CV_8UC1, cv::Scalar(1));
//Specify the ROI in the mask
cv::Mat inverseMaskROI = inverseMask(ROIrect);
//Fill the mask's ROI with 0
inverseMaskROI = cv::Scalar(0);
//Set the image to 0 in places where the mask is 1
inverseFill.setTo(cv::Scalar(0), inverseMask);
cv::namedWindow("inverseFill");
cv::imshow("inverseFill", inverseFill);
cvMoveWindow("inverseFill", 1000, 40);
// wait for key
cv::waitKey(0);
return 0;
}
Nested for loops would indeed be the quickest way.
Otherwise, consider making a buffer of identical size that's cleared using cvZero (all black). Then, setROI to the region that you care about, and cvCopy into the temporary buffer.
A bit mask with cvAnd is also a nice and clean solution.