term-owo-cpp/clip-lib/clip_win_bmp.cpp
2024-06-20 15:11:55 +01:00

348 lines
9.1 KiB
C++

// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win_bmp.h"
#include "clip.h"
#include "clip_common.h"
#include <vector>
namespace clip {
namespace win {
namespace {
unsigned long get_shift_from_mask(unsigned long mask) {
unsigned long shift = 0;
for (shift=0; shift<sizeof(unsigned long)*8; ++shift)
if (mask & (1 << shift))
return shift;
return shift;
}
} // anonymous namespace
BitmapInfo::BitmapInfo() {
// Use DIBV5 only for 32 bpp uncompressed bitmaps and when all
// masks are valid.
if (IsClipboardFormatAvailable(CF_DIBV5)) {
b5 = (BITMAPV5HEADER*)GetClipboardData(CF_DIBV5);
if (load_from(b5))
return;
}
if (IsClipboardFormatAvailable(CF_DIB)) {
bi = (BITMAPINFO*)GetClipboardData(CF_DIB);
load_from(bi);
}
}
bool BitmapInfo::load_from(BITMAPV5HEADER* b5) {
if (b5 &&
b5->bV5BitCount == 32 &&
((b5->bV5Compression == BI_RGB) ||
(b5->bV5Compression == BI_BITFIELDS &&
b5->bV5RedMask && b5->bV5GreenMask &&
b5->bV5BlueMask && b5->bV5AlphaMask))) {
width = b5->bV5Width;
height = b5->bV5Height;
bit_count = b5->bV5BitCount;
compression = b5->bV5Compression;
if (compression == BI_BITFIELDS) {
red_mask = b5->bV5RedMask;
green_mask = b5->bV5GreenMask;
blue_mask = b5->bV5BlueMask;
alpha_mask = b5->bV5AlphaMask;
}
else {
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
}
return true;
}
return false;
}
bool BitmapInfo::load_from(BITMAPINFO* bi) {
if (!bi)
return false;
width = bi->bmiHeader.biWidth;
height = bi->bmiHeader.biHeight;
bit_count = bi->bmiHeader.biBitCount;
compression = bi->bmiHeader.biCompression;
if (compression == BI_BITFIELDS) {
red_mask = *((uint32_t*)&bi->bmiColors[0]);
green_mask = *((uint32_t*)&bi->bmiColors[1]);
blue_mask = *((uint32_t*)&bi->bmiColors[2]);
if (bit_count == 32)
alpha_mask = 0xff000000;
return true;
}
if (compression == BI_RGB) {
switch (bit_count) {
case 32:
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
break;
case 24:
case 8: // We return 8bpp images as 24bpp
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
break;
case 16:
red_mask = 0x7c00;
green_mask = 0x03e0;
blue_mask = 0x001f;
break;
}
return true;
}
return false;
}
BitmapInfo::BitmapInfo(BITMAPV5HEADER* pb5) {
if (load_from(pb5))
b5 = pb5;
}
BitmapInfo::BitmapInfo(BITMAPINFO* pbi) {
if (load_from(pbi))
bi = pbi;
}
void BitmapInfo::fill_spec(image_spec& spec) const {
spec.width = width;
spec.height = (height >= 0 ? height: -height);
// We convert indexed to 24bpp RGB images to match the OS X behavior
spec.bits_per_pixel = bit_count;
if (spec.bits_per_pixel <= 8)
spec.bits_per_pixel = 24;
spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8);
spec.red_mask = red_mask;
spec.green_mask = green_mask;
spec.blue_mask = blue_mask;
spec.alpha_mask = alpha_mask;
switch (spec.bits_per_pixel) {
case 24: {
// We need one extra byte to avoid a crash updating the last
// pixel on last row using:
//
// *((uint32_t*)ptr) = pixel24bpp;
//
++spec.bytes_per_row;
// Align each row to 32bpp
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
case 16: {
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask) {
if (*mask)
*shift = get_shift_from_mask(*mask);
}
}
bool BitmapInfo::to_image(image& output_img) const {
if (!is_valid()) {
// There is no valid image. Maybe because there is no image at all
// in the clipboard when using the BitmapInfo default
// constructor. No need to report this as an error, just return
// false.
return false;
}
image_spec spec;
fill_spec(spec);
image img(spec);
switch (bit_count) {
case 32:
case 24:
case 16: {
const uint8_t* src = nullptr;
if (compression == BI_RGB ||
compression == BI_BITFIELDS) {
if (b5)
src = ((uint8_t*)b5) + b5->bV5Size;
else
src = ((uint8_t*)bi) + bi->bmiHeader.biSize;
if (compression == BI_BITFIELDS)
src += sizeof(RGBQUAD)*3;
}
if (src) {
const int src_bytes_per_row = spec.width*((bit_count+7)/8);
const int padding = (4-(src_bytes_per_row&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) {
char* dst = img.data()+y*spec.bytes_per_row;
std::copy(src, src+src_bytes_per_row, dst);
}
}
// Windows uses premultiplied RGB values, and we use straight
// alpha. So we have to divide all RGB values by its alpha.
if (bit_count == 32 && spec.alpha_mask) {
details::divide_rgb_by_alpha(img);
}
break;
}
case 8: {
assert(bi);
const int colors = (bi->bmiHeader.biClrUsed > 0 ? bi->bmiHeader.biClrUsed: 256);
std::vector<uint32_t> palette(colors);
for (int c=0; c<colors; ++c) {
palette[c] =
(bi->bmiColors[c].rgbRed << spec.red_shift) |
(bi->bmiColors[c].rgbGreen << spec.green_shift) |
(bi->bmiColors[c].rgbBlue << spec.blue_shift);
}
const uint8_t* src = (((uint8_t*)bi) + bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors);
const int padding = (4-(spec.width&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=padding) {
char* dst = img.data()+y*spec.bytes_per_row;
for (unsigned long x=0; x<spec.width; ++x, ++src, dst+=3) {
int idx = *src;
if (idx < 0)
idx = 0;
else if (idx >= colors)
idx = colors-1;
*((uint32_t*)dst) = palette[idx];
}
}
break;
}
}
std::swap(output_img, img);
return true;
}
HGLOBAL create_dibv5(const image& image) {
const image_spec& spec = image.spec();
image_spec out_spec = spec;
int palette_colors = 0;
int padding = 0;
switch (spec.bits_per_pixel) {
case 24: padding = (4-((spec.width*3)&3))&3; break;
case 16: padding = ((4-((spec.width*2)&3))&3)/2; break;
case 8: padding = (4-(spec.width&3))&3; break;
}
out_spec.bytes_per_row += padding;
// Create the BITMAPV5HEADER structure
HGLOBAL hmem =
GlobalAlloc(
GHND,
sizeof(BITMAPV5HEADER)
+ palette_colors*sizeof(RGBQUAD)
+ out_spec.bytes_per_row*out_spec.height);
if (!hmem)
return nullptr;
out_spec.red_mask = 0x00ff0000;
out_spec.green_mask = 0xff00;
out_spec.blue_mask = 0xff;
out_spec.alpha_mask = 0xff000000;
out_spec.red_shift = 16;
out_spec.green_shift = 8;
out_spec.blue_shift = 0;
out_spec.alpha_shift = 24;
BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem);
bi->bV5Size = sizeof(BITMAPV5HEADER);
bi->bV5Width = out_spec.width;
bi->bV5Height = out_spec.height;
bi->bV5Planes = 1;
bi->bV5BitCount = (WORD)out_spec.bits_per_pixel;
bi->bV5Compression = BI_RGB;
bi->bV5SizeImage = out_spec.bytes_per_row*spec.height;
bi->bV5RedMask = out_spec.red_mask;
bi->bV5GreenMask = out_spec.green_mask;
bi->bV5BlueMask = out_spec.blue_mask;
bi->bV5AlphaMask = out_spec.alpha_mask;
bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bi->bV5Intent = LCS_GM_GRAPHICS;
bi->bV5ClrUsed = 0;
switch (spec.bits_per_pixel) {
case 32: {
const char* src = image.data();
char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row;
for (long y=spec.height-1; y>=0; --y) {
const uint32_t* src_x = (const uint32_t*)src;
uint32_t* dst_x = (uint32_t*)dst;
for (unsigned long x=0; x<spec.width; ++x, ++src_x, ++dst_x) {
uint32_t c = *src_x;
int r = ((c & spec.red_mask ) >> spec.red_shift );
int g = ((c & spec.green_mask) >> spec.green_shift);
int b = ((c & spec.blue_mask ) >> spec.blue_shift );
int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
// Windows requires premultiplied RGBA values
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
*dst_x =
(r << out_spec.red_shift ) |
(g << out_spec.green_shift) |
(b << out_spec.blue_shift ) |
(a << out_spec.alpha_shift);
}
src += spec.bytes_per_row;
dst -= out_spec.bytes_per_row;
}
break;
}
default:
GlobalUnlock(hmem);
GlobalFree(hmem);
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return nullptr;
}
GlobalUnlock(hmem);
return hmem;
}
} // namespace win
} // namespace clip