servo/components/net/image/base.rs
hirschenberger ab4b34d423 Fix #5176 by premultiplying the alpha channel to the color channels
This is GIF specific. It's also done when the image is PNG but PNG is
handled separately with the PNG crate, whereas GIFs are handled by the
stb-image crate and the distinction between alpha and non-alpha-supporting
images was missing.
2015-03-11 14:52:16 +01:00

91 lines
3 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::iter::range_step;
use stb_image::image as stb_image;
use png;
use util::vec::byte_swap;
// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
pub type Image = png::Image;
static TEST_IMAGE: &'static [u8] = include_bytes!("test.jpeg");
pub fn test_image_bin() -> Vec<u8> {
TEST_IMAGE.iter().map(|&x| x).collect()
}
// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
fn byte_swap_and_premultiply(data: &mut [u8]) {
let length = data.len();
for i in range_step(0, length, 4) {
let r = data[i + 2];
let g = data[i + 1];
let b = data[i + 0];
let a = data[i + 3];
data[i + 0] = ((r as u32) * (a as u32) / 255) as u8;
data[i + 1] = ((g as u32) * (a as u32) / 255) as u8;
data[i + 2] = ((b as u32) * (a as u32) / 255) as u8;
}
}
pub fn load_from_memory(buffer: &[u8]) -> Option<Image> {
if buffer.len() == 0 {
return None;
}
if png::is_png(buffer) {
match png::load_png_from_memory(buffer) {
Ok(mut png_image) => {
match png_image.pixels {
png::PixelsByColorType::RGB8(ref mut data) => byte_swap(data.as_mut_slice()),
png::PixelsByColorType::RGBA8(ref mut data) => {
byte_swap_and_premultiply(data.as_mut_slice())
}
_ => {}
}
Some(png_image)
}
Err(_err) => None,
}
} else {
// For non-png images, we use stb_image
// Can't remember why we do this. Maybe it's what cairo wants
static FORCE_DEPTH: uint = 4;
match stb_image::load_from_memory_with_depth(buffer, FORCE_DEPTH, true) {
stb_image::LoadResult::ImageU8(mut image) => {
assert!(image.depth == 4);
// handle gif separately because the alpha-channel has to be premultiplied
if is_gif(buffer) {
byte_swap_and_premultiply(image.data.as_mut_slice());
} else {
byte_swap(image.data.as_mut_slice());
}
Some(png::Image {
width: image.width as u32,
height: image.height as u32,
pixels: png::PixelsByColorType::RGBA8(image.data)
})
}
stb_image::LoadResult::ImageF32(_image) => {
error!("HDR images not implemented");
None
}
stb_image::LoadResult::Error(e) => {
error!("stb_image failed: {}", e);
None
}
}
}
}
fn is_gif(buffer: &[u8]) -> bool {
match buffer {
[b'G',b'I',b'F',b'8', n, b'a', ..] if n == b'7' || n == b'9' => true,
_ => false
}
}