0

I want to put a local image into the clipboard using Rust. I used the clipboard-win and image crates. My code is as follows, but it doesn't work.

extern crate clipboard_win;
extern crate image;

use clipboard_win::{formats, Clipboard};
use image::GenericImageView;

fn main() {
    let img = image::open("C:\\Users\\Crash\\Desktop\\20190405221505.png").unwrap();
    Clipboard::new()
        .unwrap()
        .set(formats::CF_BITMAP, &img.raw_pixels());
}

After execution, there seems to be content in the pasteboard, but there is nothing displayed after Ctrl+V. How can I correct this code?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
tantalum
  • 13
  • 8

2 Answers2

1

You have multiple problems.

Format

A PNG format image is not a bitmap format image, even though it is a bitmap.

A thread on MSDN states:

There isn't a standardized clipboard format for PNG.

You can register your own format, but then only you can recognize the clipboard. If you use the standard bitmap or file format then more applications can accept your data.

Error handling

Clipboard::set can fail and it returns a Result. You need to handle this case. The compiler even told you about this:

warning: unused `std::result::Result` that must be used
  --> src\main.rs:11:5
   |
11 | /     Clipboard::new()
12 | |         .unwrap()
13 | |         .set(formats::CF_BITMAP, &data);
   | |________________________________________^
   |
   = note: #[warn(unused_must_use)] on by default
   = note: this `Result` may be an `Err` variant, which should be handled

Don't ignore warnings, especially when trying to debug a problem.


Unfortunately, this is as far as I got:

use clipboard_win::{formats, Clipboard}; // 2.1.2
use image::ImageOutputFormat; // 0.21.0

fn main() {
    let img = image::open("unicorn.png").unwrap();

    let mut data = Vec::new();
    img.write_to(&mut data, ImageOutputFormat::BMP)
        .expect("Unable to transform");

    Clipboard::new()
        .unwrap()
        .set(formats::CF_BITMAP, &data)
        .expect("Unable to set clipboard");
}

Writing data to a file produces a BMP that Paint can read, but the clipboard data is still invalid. While attempting to debug the differences, I ran into low-level crashes in the library, which suggests it may not be ready for general use, despite the 2.x version number.

I believe that the root problem is that

Windows expects

A handle to a bitmap (HBITMAP).

A BITMAP is a struct with a set of information about the bitmap, such as width and height. This is likely different from the on-disk format of the bitmap.

It seems plausible that fitting the bitmap data into this expected format would go a long way to solving the problem.

Another avenue is to look into using CF_DIB instead of CF_BITMAP. Contrary to the linked forum post above, CF_DIB expects a pointer to a BITMAPINFO which has a BITMAPINFOHEADER field. This makes a reference to a BI_PNG compression, which might allow you to submit a PNG without performing transformations.

See also:

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you for pointing out my question. Is there a problem with [clipboard-formats](https://learn.microsoft.com/en-us/windows/desktop/dataxchg/standard-clipboard-formats)? – tantalum Apr 07 '19 at 01:39
1

I've found this link, which describes the 54 byte long header and other BMP specifications Windows uses.

This contains some interesting things: the lines are stored bottom to top, one pixel is 4 bytes, with one unused byte, and the colors are stored in reverse, so BGR instead of RGB.

Instead of searching for the correct format conversion in an other library, based on the link I wrote some basic functions to generate the bytes from the image crate's DynamicImage type.

(The built-in BMP format of the images crate did not work on the first try, so I did this instead.)

This code is everything you need to send an image to the clipboard. This code doesn't handle transparency.

use std::ops::Range;

use clipboard_win::{formats, set_clipboard};
use image::{DynamicImage, GenericImageView};
fn main() {
    let img = image::open("image.png").unwrap();
    let data = gen_from_img(&img);
    set_clipboard(formats::Bitmap, data).unwrap();
}

fn gen_from_img(img: &DynamicImage) -> Vec<u8> {
    //Flipping image, because scan lines are stored bottom to top
    let img = img.flipv();

    //Getting the header
    let mut byte_vec = get_header(img.width(), img.height());

    for (_, _, pixel) in img.pixels() {
        //Setting the pixels, one by one

        let pixel_bytes = pixel.0;
        //One pixel is 4 bytes, BGR and unused
        byte_vec.push(pixel_bytes[2]);
        byte_vec.push(pixel_bytes[1]);
        byte_vec.push(pixel_bytes[0]);
        byte_vec.push(pixel_bytes[3]); //This is unused based on the specifications
    }

    byte_vec
}

/// Generates the header for the bitmap from the width and height of the image.
/// [Resources][http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm].
fn get_header(width: u32, height: u32) -> Vec<u8> {
    //Generating the 54 bytes long vector
    let mut vec = vec![0; 54];

    //BM, as written in specifications
    vec[0] = 66;
    vec[1] = 77;

    //File size
    let file_size = width * height * 4 + 54;
    set_bytes(&mut vec, &file_size.to_le_bytes(), 2..6);

    //Not used
    set_bytes(&mut vec, &0_u32.to_le_bytes(), 6..10);

    //Offset from the beginning of the file to the beginning of the image data
    let offset = 54_u32;
    set_bytes(&mut vec, &offset.to_le_bytes(), 10..14);

    //Size of the second part of the header
    let header_size = 40_u32;
    set_bytes(&mut vec, &header_size.to_le_bytes(), 14..18);

    //Horizontal width
    let width_bytes = width.to_le_bytes();
    set_bytes(&mut vec, &width_bytes, 18..22);

    //Vertical height
    let height_bytes = height.to_le_bytes();
    set_bytes(&mut vec, &height_bytes, 22..26);

    //Number of planes
    let planes = 1_u16;
    set_bytes(&mut vec, &planes.to_le_bytes(), 26..28);

    //Bits per pixel
    let bits_per_pixel = 32_u16;
    set_bytes(&mut vec, &bits_per_pixel.to_le_bytes(), 28..30);

    //Compression type, 0=no compression
    let compression_type = 0_u32;
    set_bytes(&mut vec, &compression_type.to_le_bytes(), 30..34);

    //Compressed size of the image, without the size of the header
    //This size is correct

    // let compressed_size = file_size - 54;

    //But when there is no compression (compression type=0), 0 is an allowed size.
    let compressed_size = 0_u32;

    set_bytes(&mut vec, &compressed_size.to_le_bytes(), 34..38);

    //Number of pixels / meter, but 0 is allowed
    let horizontal_resoultion = 0_u32;
    set_bytes(&mut vec, &horizontal_resoultion.to_le_bytes(), 38..42);

    let vertical_resolution = 0_u32;
    set_bytes(&mut vec, &vertical_resolution.to_le_bytes(), 42..46);

    //I guess the last two are for other formats/compression types
    let actually_used_colors = 0_u32;
    set_bytes(&mut vec, &actually_used_colors.to_le_bytes(), 46..50);

    let number_of_important_colors = 0_u32;
    set_bytes(&mut vec, &number_of_important_colors.to_le_bytes(), 50..54);

    vec
}

/// Replaces the bytes of the `to` slice in the specified range with the bytes of the `from` slice.
fn set_bytes(to: &mut [u8], from: &[u8], range: Range<usize>) {
    for (from_zero_index, i) in range.enumerate() {
        to[i] = from[from_zero_index];
    }
}

Andros
  • 11
  • 2