/* 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/. */ // A very basic BlobImageRasterizer that can only render a checkerboard pattern. use std::collections::HashMap; use std::sync::Arc; use std::sync::Mutex; use webrender::api::*; use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, TileOffset}; use webrender::api::units::DeviceIntRect; // Serialize/deserialize the blob. pub fn serialize_blob(color: ColorU) -> Arc> { Arc::new(vec![color.r, color.g, color.b, color.a]) } fn deserialize_blob(blob: &[u8]) -> Result { let mut iter = blob.iter(); match (iter.next(), iter.next(), iter.next(), iter.next()) { (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)), (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)), _ => Err(()), } } // perform floor((x * a) / 255. + 0.5) see "Three wrongs make a right" for derivation fn premul(x: u8, a: u8) -> u8 { let t = (x as u32) * (a as u32) + 128; ((t + (t >> 8)) >> 8) as u8 } // This is the function that applies the deserialized drawing commands and generates // actual image data. fn render_blob( color: ColorU, descriptor: &BlobImageDescriptor, tile: TileOffset, _tile_size: TileSize, dirty_rect: &BlobDirtyRect, ) -> BlobImageResult { // Allocate storage for the result. Right now the resource cache expects the // tiles to have have no stride or offset. let buf_size = descriptor.rect.area() * descriptor.format.bytes_per_pixel(); let mut texels = vec![0u8; (buf_size) as usize]; // Generate a per-tile pattern to see it in the demo. For a real use case it would not // make sense for the rendered content to depend on its tile. let tile_checker = (tile.x % 2 == 0) != (tile.y % 2 == 0); let dirty_rect = dirty_rect.to_subrect_of(&descriptor.rect); // We want the dirty rect local to the tile rather than the whole image. let tx: BlobToDeviceTranslation = (-descriptor.rect.min.to_vector()).into(); let rasterized_rect = tx.transform_box(&dirty_rect); for y in rasterized_rect.min.y .. rasterized_rect.max.y { for x in rasterized_rect.min.x .. rasterized_rect.max.x { // Apply the tile's offset. This is important: all drawing commands should be // translated by this offset to give correct results with tiled blob images. let x2 = x + descriptor.rect.min.x; let y2 = y + descriptor.rect.min.y; // Render a simple checkerboard pattern let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 }; // ..nested in the per-tile checkerboard pattern let tc = if tile_checker { 0 } else { (1 - checker) * 40 }; match descriptor.format { ImageFormat::BGRA8 => { let a = color.a * checker + tc; let pixel_offset = ((y * descriptor.rect.width() + x) * 4) as usize; texels[pixel_offset ] = premul(color.b * checker + tc, a); texels[pixel_offset + 1] = premul(color.g * checker + tc, a); texels[pixel_offset + 2] = premul(color.r * checker + tc, a); texels[pixel_offset + 3] = a; } ImageFormat::R8 => { texels[(y * descriptor.rect.width() + x) as usize] = color.a * checker + tc; } _ => { return Err(BlobImageError::Other( format!("Unsupported image format {:?}", descriptor.format), )); } } } } Ok(RasterizedBlobImage { data: Arc::new(texels), rasterized_rect, }) } /// See rawtest.rs. We use this to test that blob images are requested the right /// amount of times. pub struct BlobCallbacks { pub request: Box, } impl BlobCallbacks { pub fn new() -> Self { BlobCallbacks { request: Box::new(|_|()) } } } pub struct CheckerboardRenderer { image_cmds: HashMap, callbacks: Arc>, } impl CheckerboardRenderer { pub fn new(callbacks: Arc>) -> Self { CheckerboardRenderer { callbacks, image_cmds: HashMap::new(), } } } impl BlobImageHandler for CheckerboardRenderer { fn create_similar(&self) -> Box { Box::new(CheckerboardRenderer::new(Arc::clone(&self.callbacks))) } fn add(&mut self, key: BlobImageKey, cmds: Arc, _visible_rect: &DeviceIntRect, tile_size: TileSize) { self.image_cmds .insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size)); } fn update(&mut self, key: BlobImageKey, cmds: Arc, _visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) { // Here, updating is just replacing the current version of the commands with // the new one (no incremental updates). self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap(); } fn delete(&mut self, key: BlobImageKey) { self.image_cmds.remove(&key); } fn delete_font(&mut self, _key: FontKey) {} fn delete_font_instance(&mut self, _key: FontInstanceKey) {} fn clear_namespace(&mut self, _namespace: IdNamespace) {} fn prepare_resources( &mut self, _services: &dyn BlobImageResources, requests: &[BlobImageParams], ) { if !requests.is_empty() { (self.callbacks.lock().unwrap().request)(requests); } } fn create_blob_rasterizer(&mut self) -> Box { Box::new(Rasterizer { image_cmds: self.image_cmds.clone() }) } fn enable_multithreading(&mut self, _enable: bool) {} } struct Command { request: BlobImageRequest, color: ColorU, descriptor: BlobImageDescriptor, tile: TileOffset, tile_size: TileSize, dirty_rect: BlobDirtyRect, } struct Rasterizer { image_cmds: HashMap, } impl AsyncBlobImageRasterizer for Rasterizer { fn rasterize( &mut self, requests: &[BlobImageParams], _low_priority: bool ) -> Vec<(BlobImageRequest, BlobImageResult)> { let requests: Vec = requests.iter().map( |item| { let (color, tile_size) = self.image_cmds[&item.request.key]; Command { request: item.request, color, tile_size, tile: item.request.tile, descriptor: item.descriptor, dirty_rect: item.dirty_rect, } } ).collect(); requests.iter().map(|cmd| { (cmd.request, render_blob(cmd.color, &cmd.descriptor, cmd.tile, cmd.tile_size, &cmd.dirty_rect)) }).collect() } }