Blue and red swapping in skia on linux

use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use skia_safe::{surfaces,Color, Paint,PaintStyle, Font, Typeface, FontStyle, Data};

fn main(){

    let event_loop = EventLoop::new();
    // event_loop.set_control_flow(ControlFlow::Wait);
    let window = WindowBuilder::new().build(&event_loop).unwrap();
    let context = unsafe { Context::new(&window) }.unwrap();
    let mut surface = unsafe { Surface::new(&context, &window) }.unwrap();


    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Wait;
    
        match event {
            
            Event::RedrawRequested(window_id) if window_id == window.id() => {
                let (width, height) = {
                    let size = window.inner_size();
                    (size.width, size.height)
                };
                surface
                    .resize(
                        NonZeroU32::new(width).unwrap(),
                        NonZeroU32::new(height).unwrap(),
                    )
                    .unwrap();

                let mut visible_buffer = surfaces::raster_n32_premul((width as i32, height as i32)).expect("surface");
                visible_buffer.canvas().clear(Color::WHITE);
                
                let mut paint = Paint::default();
                paint.set_color(Color::BLUE);
                // paint.set_color(Color::from_rgb(55, 250, 80));
                // paint.set_color(Color::from_argb(255, 0, 0,255));
                paint.set_anti_alias(true);
                paint.set_style(PaintStyle::Fill);
                
                visible_buffer.canvas().draw_circle(((width/2) as f32, (height/2) as f32),100.0 , &paint);

                let mut fpaint = Paint::default();
                // fpaint.set_color(Color::from_rgb(80, 150, 250));
                fpaint.set_color(Color::RED);
                // fpaint.set_color(Color::from_argb(255, 0, 200,0));
                fpaint.set_anti_alias(true);
                fpaint.set_style(PaintStyle::Fill);

                
                let font_raw = std::fs::read("src/fonts/ColombiaItalic-BWGZB.ttf").unwrap();
                let font_data = unsafe { Data::new_bytes(font_raw.as_slice()) };
                let font = Font::from_typeface(Typeface::from_data(font_data, None).unwrap(), 30.0);

                visible_buffer.canvas().draw_str("my string", ((width/2) as f32, (height/2) as f32), &font, &fpaint);
                
                
                let pixmap = visible_buffer.canvas().peek_pixels().unwrap().bytes().unwrap();
            

                let mut buffer = surface.buffer_mut().unwrap();
                for index in 0..(width * height) as usize {
                    buffer[index] = pixmap[index * 4 + 2] as u32
                        | (pixmap[index * 4 + 1] as u32) << 8
                        | (pixmap[index * 4] as u32) << 16;
                }
                buffer.present().unwrap();
            }

            Event::WindowEvent { event, .. } => match event {
                WindowEvent::CloseRequested => control_flow.set_exit(),
                // WindowEvent::CursorMoved { position,device_id , modifiers } 
                // => {}
            _ => {}
        }_ => {}



    };
    });
    }

A window is supposed to appear containing a blue circle with red text inside it, but what happens is that the circle is red and the text is blue.

you probably used the wrong pixel format, i.e. BGR vs RGB.

softbuffer requires a very specific pixel format which you are unable to deviate from.

skia_safe::surfaces::raster_n32_premul() creates a "Surface that matches crate::PMColor, the native pixel arrangement on the platform." Which just says the pixel arrangement is "whatever the platform wants", and that's unlikely to be identical to "what softbuffer wants".

It looks like you can use skia_safe::surfaces::raster() to create a surface with a specific pixel layout with the surface_props parameter.


Edit: Reading the skia code more closely, the surface part looks like it is only for subpixel antialiasing. The color format is specified to be "native" and is set in ImageInfo. I don't know what "native" means, precisely, but if it's the GPU's preferred surface texture format, that is usually Bgra8UnormSrgb.

2 Likes

Hi, I used the Pixels library instead of Softbuffer, but the problem still remains the same

// #![deny(clippy::all)]
// #![forbid(unsafe_code)]

use error_iter::ErrorIter as _;
use log::error;
use pixels::{Error, Pixels, SurfaceTexture};
use std::time::Instant;
// use tiny_skia::Pixmap;
use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use winit_input_helper::WinitInputHelper;

use skia_safe::{surfaces,Color, Paint,PaintStyle, Font, Typeface, FontStyle, Data, FontMgr, ColorType, ImageInfo, AlphaType, ColorSpace, SurfaceProps, SurfacePropsFlags,PixelGeometry};

// mod shape;

const WIDTH: u32 = 500;
const HEIGHT: u32 = 500;

fn main() -> Result<(), Error> {
    // env_logger::init();
    let event_loop = EventLoop::new();
    let mut input = WinitInputHelper::new();
    let window = {
        let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
        WindowBuilder::new()
            .with_title("Hello tiny-skia")
            .with_inner_size(size)
            .with_min_inner_size(size)
            .build(&event_loop)
            .unwrap()
    };

    let mut pixels = {
        let window_size = window.inner_size();
        let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);

        Pixels::new(WIDTH, HEIGHT, surface_texture)?
    };



    let mut visible_buffer = surfaces::raster_n32_premul((WIDTH as i32, HEIGHT as i32)).expect("surface");
    // let ii = ImageInfo::new((width as i32,height as i32), ColorType::RGBA8888, AlphaType::Premul, ColorSpace::new_srgb());
    // let mut visible_buffer = surfaces::raster(&ii,(100) as usize, Some(&SurfaceProps::default())).expect("surface");






    let now = Instant::now();

    event_loop.run(move |event, _, control_flow| {
        // Draw the current frame
        if let Event::RedrawRequested(_) = event {


            visible_buffer.canvas().clear(Color::WHITE);
    
            let mut paint = Paint::default();
            
            paint.set_color(Color::BLUE);
            
            // paint.set_color(Color::from_rgb(55, 250, 80));
            // paint.set_color(Color::from_argb(255, 0, 0,255));
            // paint.set_anti_alias(true);
            paint.set_style(PaintStyle::Fill);
            
            visible_buffer.canvas().draw_circle(((WIDTH/2) as f32, (HEIGHT/2) as f32),100.0 , &paint);
        
            let mut fpaint = Paint::default();
            // fpaint.set_color(Color::from_rgb(80, 150, 250));
            fpaint.set_color(Color::RED);
            // fpaint.set_color(Color::from_argb(255, 0, 200,0));
            // fpaint.set_anti_alias(true);
            fpaint.set_style(PaintStyle::Fill);
        
            // let font = Font::from_typeface(Typeface::from_name("Times", FontStyle::bold_italic()).unwrap(), 20.0);
            // let font_mgr = FontMgr::new();
            let font_raw = std::fs::read("src/fonts/ColombiaItalic-BWGZB.ttf").unwrap();
            let font_data = unsafe { Data::new_bytes(font_raw.as_slice()) };
            let font = Font::from_typeface(Typeface::from_data(font_data, None).unwrap(), 30.0);
        
            visible_buffer.canvas().draw_str("string works??", ((WIDTH/2) as f32, (HEIGHT/2) as f32), &font, &fpaint);
            
            
            
            let pixmap = visible_buffer.canvas().peek_pixels().unwrap().bytes().unwrap();


            pixels.frame_mut().copy_from_slice(pixmap);
            if let Err(err) = pixels.render() {
                log_error("pixels.render", err);
                *control_flow = ControlFlow::Exit;
                return;
            }
        }

        // Handle input events
        if input.update(&event) {
            // Close events
            if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() {
                *control_flow = ControlFlow::Exit;
                return;
            }

            // Resize the window
            if let Some(size) = input.window_resized() {
                if let Err(err) = pixels.resize_surface(size.width, size.height) {
                    log_error("pixels.resize_surface", err);
                    *control_flow = ControlFlow::Exit;
                    return;
                }
            }

            // Update internal state and request a redraw
            // shape::draw(&mut drawing, now.elapsed().as_secs_f32());
            window.request_redraw();
        }
    });
}

fn log_error<E: std::error::Error + 'static>(method_name: &str, err: E) {
    error!("{method_name}() failed: {err}");
    for source in err.sources().skip(1) {
        error!("  Caused by: {source}");
    }
}

I think this might be the solution, but there are no details on how to do it, How do I determine the color type when reading pixels?

You can use PixelsBuilder to set the texture color format: PixelsBuilder in pixels - Rust It defaults to Rgba8UnormSrgb, which is usually what people expect when they want to poke individual pixels into a texture.

But again, raster_n32_premul() will choose a color format for you based on the platform. So, while you could change pixels's texture format to Bgra8UnormSrgb to match what skia chooses on your local platform, there is no guarantee that it will always choose that format on every platform.

Thank you very much, I was finally able to solve the problem.
I changed:

 let mut visible_buffer = surfaces::raster_n32_premul((width as i32, height as i32)).expect("surface");

to:

let ii = ImageInfo::new((width as i32,height as i32), ColorType::RGBA8888, AlphaType::Premul, ColorSpace::new_srgb());
let mut visible_buffer = surfaces::raster(&ii,(0) as usize, Some(&SurfaceProps::default())).expect("surface");