The right way to capture screenshot using Rust - winapi

I have been looking for an answer to this question for several days and so on until the end, I have not completely figured it out. It is worth clarifying that I am looking for a way not just to save the screenshot as a file, but to get an array of bytes and array of bytes decoded to png/jpg or another lightweight image format.
The first way i tried was screenshot-rs crate, code like this:
let s = match get_screenshot(0) {
Ok(it) => it,
_ => return,
};
let mut buf = Vec::<u8>::new();
let encoder = PngEncoder::new(&mut buf);
unsafe {
match encoder.encode(
std::slice::from_raw_parts(s.raw_data(), s.raw_len()),
s.width() as u32,
s.height() as u32,
image::ColorType::Rgba8,
) {
Ok(it) => it,
_ => return,
};
}
(I use png encoder too)
Formally, this code works but works very slowly (around ~5 sec) and producec screenshot with switched blue and red color channels.
After this i start trying to use winapi to capture screenshot, but my knowledge about winapi, hundreds of data types and what i have to do now (this doesnt work properly)
let x1 = GetSystemMetrics(78);
let y1 = GetSystemMetrics(79);
let x2 = GetSystemMetrics(76);
let y2 = GetSystemMetrics(77);
let w = x2 - x1;
let h = y2 - y1;
let hdc_screen = GetDC(ptr::null_mut());
let hdc = CreateCompatibleDC(hdc_screen);
let h_bitmap = CreateCompatibleBitmap(hdc_screen, w, h);
let obj = SelectObject(hdc, h_bitmap as HGDIOBJ);
let status = BitBlt(hdc, 0, 0, w, h, hdc_screen, x1, y1, SRCCOPY);

Related

Dithered image larger than original (using the Rust image crate)

I'm learning Rust and wanted to try my hand at error diffusion dithering. I've got it working, but the dithered file ends up bigger than the original, which is the opposite of what's supposed to happen. The original JPEG image is 605 KB big, but the dithered image has a whopping 2.57 MB. My knowledge of the image crate is very limited and I found all the various structs for representing images confusing, so I must be missing something regarding the API.
Here's the code for dithering the image (included are only parts which I deemed relevant):
impl DiffusionKernel<'_> {
pub const FLOYD_STEINBERG: DiffusionKernel<'_> = // Constructor
fn distribute_error(
&self,
error: &(i16, i16, i16),
image: &mut DynamicImage,
width: u32,
height: u32,
x: u32,
y: u32,
) {
for target in self.targets /* "targets" are the pixels where to distribute the error */ {
// Checks if the target x and y are in the bounds of the image
// Also returns the x and y coordinates of the pixel, because the "target" struct only describes the offset of the target pixel from the pixel being currently processed
let (is_valid_target, target_x, target_y) =
DiffusionKernel::is_valid_target(target, width, height, x, y);
if is_valid_target == false {
continue;
}
let target_pix = image.get_pixel(target_x, target_y);
// Distribute the error to the target_pix
let new_pix = Rgba::from([new_r, new_g, new_b, 255]);
image.put_pixel(target_x, target_y, new_pix);
}
}
pub fn diffuse(&self, bit_depth: u8, image: &mut DynamicImage) {
let width = image.width();
let height = image.height();
for x in 0..width {
for y in 0..height {
let pix = image.get_pixel(x, y);
let pix_quantized = ColorUtil::reduce_color_bit_depth(pix, bit_depth); // Quantizes the color
let error = (
pix.0[0] as i16 - pix_quantized.0[0] as i16,
pix.0[1] as i16 - pix_quantized.0[1] as i16,
pix.0[2] as i16 - pix_quantized.0[2] as i16,
);
image.put_pixel(x, y, pix_quantized);
self.distribute_error(&error, image, width, height, x, y);
}
}
// Distributing the error ends up creating colors like 7, 7, 7, or 12, 12, 12 instead of 0, 0, 0 for black,
// so here I'm just ensuring that the colors are correctly quantized.
// I think the algorithm shouldn't behave like this, I'll try to fix it later.
for x in 0..width {
for y in 0..height {
let pix = image.get_pixel(x, y);
let pix_quantized = ColorUtil::reduce_color_bit_depth(pix, bit_depth);
image.put_pixel(x, y, pix_quantized);
}
}
}
}
Here's the code for loading and saving the image:
let format = "jpg";
let path = String::from("C:\\...\\Cat.".to_owned() + format);
let trimmed_path = path.trim(); // This needs to be here if I'm getting the path from the console
let bfr = Reader::open(trimmed_path)
.unwrap()
.with_guessed_format()
.unwrap();
let mut dynamic = bfr.decode().unwrap();
// dynamic = dynamic.grayscale();
error_diffusion::DiffusionKernel::FLOYD_STEINBERG.diffuse(1, &mut dynamic);
dynamic
.save(trimmed_path.to_owned() + "_dithered." + format)
.expect("There was an error saving the image.");
Ok, so I got back to trying to figure this out, and it looks like you just need to use an image encoder like the PngEncoder and a file to write to in order to lower the bit depth of an image. The encoder takes bytes, not pixels, but thankfully, images have a as_bytes method which returns what you need.
Here's the code:
let img = image::open(path).expect("Failed to open image.");
let (width, height) = img.dimensions();
let writer = File::create(path.to_owned() + "_out.png").unwrap();
// This is the best encoder configuration for black/white images, which is my output
// Grayscale with multiple colors -> black/white using dithering
let encoder = PngEncoder::new_with_quality(writer, CompressionType::Best, FilterType::NoFilter);
encoder
.write_image(img.as_bytes(), width, height, ColorType::L8)
.expect("Failed to write image.");

Rust-SDL2: Is there a way to draw a singular pixel to screen?

I'm making a project in Rust-SDL2, and I'm not exactly sure how to draw an individual pixel to the window. I tried looking into sdl2::rect::Point, but the documentation is rather confusing for me.
It seems the documentation for the sdl2 crate is written for someone who already knows how sdl works and just wants to use it in Rust. You may want to run through a few tutorials first…
Then again, just string-searching for point led me to the right function.
Quick example with some flickering points:
use rand::Rng;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::Point;
use std::time::Duration;
pub fn main() {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem
.window("rust-sdl2 demo", 800, 600)
.position_centered()
.build()
.unwrap();
let mut canvas = window.into_canvas().build().unwrap();
let mut event_pump = sdl_context.event_pump().unwrap();
let mut i = 0;
let mut rng = rand::thread_rng();
'running: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
..
} => break 'running,
_ => {}
}
}
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
i = (i + 1) % 255;
canvas.set_draw_color(Color::RGB(i, 64, 255 - i));
let (w, h) = canvas.output_size().unwrap();
let mut points = [Point::new(0, 0); 256];
points.fill_with(|| Point::new(rng.gen_range(0..w as i32), rng.gen_range(0..h as i32)));
// For performance, it's probably better to draw a whole bunch of points at once
canvas.draw_points(points.as_slice()).unwrap();
canvas.present();
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); // sloppy FPS limit
}
}

How to save ndarray in Rust as image?

I have ndarray
let mut a = Array3::<u8>::from_elem((50, 40, 3), 3);
and I use image library
let mut imgbuf = image::ImageBuffer::new(50, 40);
How could I save my ndarray as image ?
If there is better image library then image for this I could use it.
The easiest way is to ensure that the array follows is in standard layout (C-contiguous) with the image dimensions in the order (height, width, channel) order (HWC), or in an equivalent memory layout. This is necessary because image expects rows to be contiguous in memory.
Then, build a RgbImage using the type's from_raw function.
use image::RgbImage;
use ndarray::Array3;
fn array_to_image(arr: Array3<u8>) -> RgbImage {
assert!(arr.is_standard_layout());
let (height, width, _) = arr.dim();
let raw = arr.into_raw_vec();
RgbImage::from_raw(width as u32, height as u32, raw)
.expect("container should have the right size for the image dimensions")
}
Example of use:
let mut array: Array3<u8> = Array3::zeros((200, 250, 3)); // 250x200 RGB
for ((x, y, z), v) in array.indexed_iter_mut() {
*v = match z {
0 => y as u8,
1 => x as u8,
2 => 0,
_ => unreachable!(),
};
}
let image = array_to_image(array);
image.save("out.png")?;
The output image:
Below are a few related helper functions, in case they are necessary.
Ndarrays can be converted to standard layout by calling the method as_standard_layout, available since version 0.13.0. Before this version, you would need to collect each array element into a vector and rebuild the array, like so:
fn to_standard_layout<A, D>(arr: Array<A, D>) -> Array<A, D>
where
A: Clone,
D: Dimension,
{
let v: Vec<_> = arr.iter().cloned().collect();
let dim = arr.dim();
Array::from_shape_vec(dim, v).unwrap()
}
Moreover, converting an ndarray in the layout (width, height, channel) to (height, width, channel) is also possible by swapping the first two axes and making the array C-contiguous afterwards:
fn wh_to_hw(mut arr: Array3<u8>) -> Array3<u8> {
arr.swap_axes(0, 1);
arr.as_standard_layout().to_owned()
}

Low framerate when running Piston example

I'm building a simple 2D game in Rust using Piston. I used examples from the Piston documentation and expanded it and it works quite well. However, I get pretty bad performance:
Drawing only 2 squares gives me a framerate of about 30-40 FPS
Drawing 5 000 squares gives me a framerate of about 5 FPS
This is on a Core i7 # 2.2GHz running Windows 10. Rust version 1.8, Piston version 0.19.0.
Is this expected or have I made any mistakes in my code? Am I even measuring the FPS correctly?
extern crate piston_window;
extern crate piston;
extern crate rand;
use piston_window::*;
use rand::Rng;
fn main() {
const SIZE: [u32; 2] = [600,600];
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
const NUM: u32 = 1000; //change this to change number of polygons
const SQUARESIZE: f64 = 10.0;
// Create an Glutin window.
let window: PistonWindow = WindowSettings::new("test",SIZE)
.exit_on_esc(true)
.build()
.unwrap();
let mut frames = 0;
let mut passed = 0.0;
let mut rng = rand::thread_rng();
for e in window {
if let Some(_) = e.render_args() {
e.draw_2d(|c, g| {
//clear the screen.
clear(GREEN, g);
for i in 0..NUM {
//setting up so that it looks pretty
let x = (i % SIZE[0]) as f64;
let y = (i % SIZE[1]) as f64;
let fill = (x / (SIZE[0] as f64)) as f32;
let color: [f32; 4] = [fill,1.0-fill,fill,fill];
let x = rng.gen_range::<f64>(0.0,SIZE[0] as f64);
//draw the square
let square = rectangle::square(0.0, 0.0, SQUARESIZE);
let transform = c.transform.trans(x-SQUARESIZE/2.0,y-SQUARESIZE/2.0);
rectangle(color, square, transform, g);
}
frames+=1;
});
}
if let Some(u) = e.update_args() {
passed += u.dt;
if passed > 1.0 {
let fps = (frames as f64) / passed;
println!("FPS: {}",fps);
frames = 0;
passed = 0.0;
}
}
}
}
Thank you for your help.
EDIT: taskmgr tells me that it only uses about 17K memory, but one of my physical CPU cores maxes out when the FPS drops below about 20.
EDIT 2: Changed the code to a complete working example.

find the white/ black pixels in specific region javacv

I have tried this code. 540 is the left most x value of the box,3 is left most y value of the box,262 - width ,23 -height of the region which I am going to calculate the ratio of the white/black pixels. What I really wanted to do is detect the number of white/black pixel ratio in a specific region.I have calculate the coordinates for each cell (regions which I am going to specified)and try with this code.But the error in counting.
Can I please have an idea about this issue please..
I am really stuck here with my final year project.
CvSize cvSize = cvSize(img.width(), img.height());
IplImage image = cvCreateImage(cvSize, IPL_DEPTH_8U, 1);
IplImage image2 = cvCreateImage(cvSize, IPL_DEPTH_8U, 3);
cvCvtColor(image2, image, CV_RGB2GRAY);
cvSetImageROI(image2, cvRect(540,3,262,23));
//IplImage image2 = cvCreateImage(cvSize, IPL_DEPTH_8U, 3);
//
//cvCvtColor(arg0, arg1, arg2)
// cvCvtColor(image2, image, CV_RGB2GRAY);
//cvThreshold(image, image, 128, 255, CV_THRESH_BINARY);
CvLineIterator iterator = new CvLineIterator();
double sum = 0, green_sum = 0, red_sum = 0;
CvPoint p2 = new CvPoint(802,3);
CvPoint p1 = new CvPoint(540,26);
int lineCount = cvInitLineIterator(image2, p1, p2, iterator, 8, 0 );
for (int i = 0; i < lineCount; i++) {
sum += iterator.ptr().get() & 0xFF;
}
System.out.println("sum................"+sum);
CV_NEXT_LINE_POINT(iterator);
}
}
it gave the result as sum................0.0
I have really stuck with this..can you please give any solution for this issue please
Move CV_NEXT_LINE_POINT(iterator); line inside the for loop. Then it should work.

Resources