document more code, fix some code smells
This commit is contained in:
parent
467f62d381
commit
d309181281
11 changed files with 133 additions and 66 deletions
|
|
@ -21,6 +21,8 @@ use DrawColor::*;
|
|||
impl DrawColor {
|
||||
pub fn menu(app: &mut App, ui: &mut Ui) {
|
||||
let mut clicked = false;
|
||||
// rust does not have reflection at the moment so this looks a bit ugly and repetitive
|
||||
// adds button to the menu
|
||||
clicked |= ui.radio_value(&mut app.color, Black, "Black").clicked();
|
||||
clicked |= ui.radio_value(&mut app.color, White, "White").clicked();
|
||||
clicked |= ui.radio_value(&mut app.color, Red, "Red").clicked();
|
||||
|
|
@ -31,6 +33,7 @@ impl DrawColor {
|
|||
clicked |= ui.radio_value(&mut app.color, Brown, "Brown").clicked();
|
||||
clicked |= ui.radio_value(&mut app.color, Aqua, "Aqua").clicked();
|
||||
clicked |= ui.radio_value(&mut app.color, Purple, "Purple").clicked();
|
||||
// if any have been clicked, change the color to it
|
||||
if clicked {
|
||||
app.draw.px = app.color.into_color();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl ChangeRect {
|
|||
pub fn push(&mut self, x: usize, y: usize) {
|
||||
self.count += 1;
|
||||
if self.changelist.is_some() && self.count >= self.max_changelist_len {
|
||||
self.changelist = None;
|
||||
self.changelist = None; // changelist has "overflown" (too much to update single points)
|
||||
}
|
||||
if let Some(ref mut changelist) = self.changelist {
|
||||
changelist.push([x, y]);
|
||||
|
|
@ -47,6 +47,8 @@ impl ChangeRect {
|
|||
self.empty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// expand area to include this point
|
||||
if x < self.min[0] {
|
||||
self.min[0] = x;
|
||||
}
|
||||
|
|
@ -62,12 +64,14 @@ impl ChangeRect {
|
|||
}
|
||||
|
||||
pub fn all(&mut self, rect: Rect) {
|
||||
// only pushes the corners as an optimization
|
||||
self.push(rect.min.x as usize, rect.min.y as usize);
|
||||
self.push(rect.max.x as usize, rect.max.y as usize);
|
||||
self.changelist = None;
|
||||
self.count += rect.area() as usize - 2;
|
||||
self.changelist = None; // force "overflown" changelist
|
||||
self.count += rect.area() as usize - 2; // add other pixels in rectangle that werent added by push
|
||||
}
|
||||
|
||||
/// "takes" the changes, resetting this struct and returning the area/pixels to update
|
||||
pub fn take(&mut self) -> ChangedRect {
|
||||
self.empty = true;
|
||||
let count = self.count;
|
||||
|
|
@ -103,7 +107,7 @@ impl<T: Copy + Sized> FlatArea<T> for Array<T, 2> {
|
|||
// SAFETY: [all unsafe operations explained in further comments]
|
||||
unsafe {
|
||||
let mut r_ptr = r.as_mut_ptr();
|
||||
// SAFETY: Every element will be overwritten
|
||||
// SAFETY: Every element will be overwritten => no garbage data will be left
|
||||
r.set_len(size[0] * size[1]);
|
||||
for i in 0..size[1] {
|
||||
let idx = start[0] + (start[1] + i) * y_len;
|
||||
|
|
@ -114,7 +118,7 @@ impl<T: Copy + Sized> FlatArea<T> for Array<T, 2> {
|
|||
self_flat[idx..]
|
||||
.as_ptr()
|
||||
.copy_to_nonoverlapping(r_ptr, size[0]);
|
||||
r_ptr = r_ptr.offset(size[0] as isize);
|
||||
r_ptr = r_ptr.add(size[0]);
|
||||
}
|
||||
}
|
||||
r
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ impl Default for Debug {
|
|||
}
|
||||
}
|
||||
|
||||
// bad (but good-enough for this purpose) random number generator
|
||||
fn rand(rand: &mut u64) -> u8 {
|
||||
let state = *rand;
|
||||
let r = ((state & 0b1111) << 60) + ((state >> 60) & 0b1111);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub enum DialogAction {
|
|||
}
|
||||
|
||||
impl App {
|
||||
// handles Open
|
||||
pub fn open_file(&mut self) {
|
||||
let mut dialog = egui_file::FileDialog::open_file(None);
|
||||
dialog.open();
|
||||
|
|
@ -15,6 +16,7 @@ impl App {
|
|||
self.dialog = Some(dialog);
|
||||
}
|
||||
|
||||
// handles Save and SaveAs
|
||||
pub fn save_file(&mut self, ask_name: bool) {
|
||||
if ask_name || self.filename.is_none() {
|
||||
let mut dialog = egui_file::FileDialog::save_file(None);
|
||||
|
|
@ -26,21 +28,16 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
// called on app update to update dialogs too
|
||||
pub fn handle_dialogs(&mut self, ctx: &Context) {
|
||||
if let Some(ref mut d) = self.dialog {
|
||||
if d.show(ctx).selected() {
|
||||
if let Some(file) = d.path() {
|
||||
if let Some(ref mut dialog) = self.dialog {
|
||||
if dialog.show(ctx).selected() {
|
||||
if let Some(file) = dialog.path() {
|
||||
self.filename = Some(file.to_str().expect("invalid file name").to_owned());
|
||||
// do IO operations associated with dialog
|
||||
match self.dialog_action.as_ref().unwrap() {
|
||||
DialogAction::Open => {
|
||||
self.filename =
|
||||
Some(file.to_str().expect("invalid file name").to_owned());
|
||||
self.load();
|
||||
}
|
||||
DialogAction::Save => {
|
||||
self.filename =
|
||||
Some(file.to_str().expect("invalid file name").to_owned());
|
||||
self.save();
|
||||
}
|
||||
DialogAction::Open => self.load(),
|
||||
DialogAction::Save => self.save(),
|
||||
};
|
||||
self.dialog_action = None;
|
||||
self.dialog = None;
|
||||
|
|
|
|||
70
src/draw.rs
70
src/draw.rs
|
|
@ -1,3 +1,5 @@
|
|||
//! These are quite complex, but I will do my best to explain them anyway.
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use egui::Color32;
|
||||
|
|
@ -87,12 +89,14 @@ impl App {
|
|||
self.image[[x, y]] = Color32::from_rgb((px >> 16) as u8, (px >> 8) as u8, px as u8);
|
||||
self.changes.push(x, y);
|
||||
}
|
||||
|
||||
/// Does not ignore pixels out of bounds, panic!s instead.
|
||||
pub fn set_px_unchecked(&mut self, x: usize, y: usize, col: Color32) {
|
||||
self.image[[x, y]] = col;
|
||||
self.changes.push(x, y);
|
||||
}
|
||||
|
||||
/// Draws a dot of arbitrary size (size 1 has no corners, all others do)
|
||||
/// Draws a dot of arbitrary size. This is one px for a size of 0, a plus for size 1, and a rectangle of side length (size - 1) * 2
|
||||
pub fn draw_dot(&mut self, draw: DrawParams) {
|
||||
self.set_px(draw.offset(0, 0));
|
||||
|
||||
|
|
@ -131,27 +135,32 @@ impl App {
|
|||
size: size2,
|
||||
..
|
||||
} = draw2;
|
||||
let dx = x2 as f32 - x1 as f32;
|
||||
let dy = y2 as f32 - y1 as f32;
|
||||
let dsize = size2 as f32 - size1 as f32;
|
||||
let dist = (dx * dx + dy * dy).sqrt();
|
||||
let step_x = dx / dist;
|
||||
let step_y = dy / dist;
|
||||
let step_size = dsize / dist;
|
||||
let dx = x2 as f32 - x1 as f32; // the offset in x direction
|
||||
let dy = y2 as f32 - y1 as f32; // the offset in y direction
|
||||
let dsize = size2 as f32 - size1 as f32; // the change in size over the distance
|
||||
let dist = (dx * dx + dy * dy).sqrt(); // the distance
|
||||
let step_x = dx / dist; // the change of x over a distance of 1 pixel
|
||||
let step_y = dy / dist; // the change of y over a distance of 1 pixel
|
||||
let step_size = dsize / dist; // the change in size over a distance of 1 pixel
|
||||
// the values as floats
|
||||
let mut fx = x1 as f32;
|
||||
let mut fy = y1 as f32;
|
||||
let mut fsize = size1 as f32;
|
||||
// loop until distance is reached, but overshoot
|
||||
for _ in 0..(dist + 1.0) as usize {
|
||||
// draw
|
||||
func(
|
||||
self,
|
||||
draw1.at_sized(fx as usize, fy as usize, fsize.round() as usize),
|
||||
);
|
||||
if fx as usize == x2 && fy as usize == y2 {
|
||||
break;
|
||||
}
|
||||
// modify values by the needed amount
|
||||
fx += step_x;
|
||||
fy += step_y;
|
||||
fsize += step_size;
|
||||
// if arrived at destination, stop (this is why the overshoot is not a problem)
|
||||
if fx as usize == x2 && fy as usize == y2 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +170,8 @@ impl App {
|
|||
self.last_mouse_pos = Some(draw);
|
||||
}
|
||||
|
||||
/// draws a polygon (or circle) by drawing around a center point at angles in increments of π*2 / n
|
||||
/// Draws an n-gon (polygon) with an arbitrary rotation, radius, and amount of corners (n)
|
||||
/// by drawing around a center point at angles in increments of π*2 / n
|
||||
pub fn draw_ngon(
|
||||
&mut self,
|
||||
draw: DrawParams,
|
||||
|
|
@ -170,26 +180,40 @@ impl App {
|
|||
mut radius_y: f32,
|
||||
begin_angle: f32,
|
||||
) {
|
||||
// n = 0 => draw a circle
|
||||
if n == 0 {
|
||||
n = (radius_x.abs().max(radius_y.abs()) * PI) as usize;
|
||||
}
|
||||
let begin_angle = (begin_angle - 90.0) / 180.0 * PI;
|
||||
if n == 4 {
|
||||
radius_x /= begin_angle.cos();
|
||||
radius_y /= begin_angle.sin();
|
||||
n = (radius_x.abs().max(radius_y.abs()) * PI * 2.0) as usize; // circle can be approximated by having as many corners as pixels
|
||||
}
|
||||
|
||||
// convert rotation to radians
|
||||
let begin_angle = begin_angle / 180.0 * PI /*start at top:*/ + PI;
|
||||
|
||||
// to make pulling the usual shapes feel more natural
|
||||
if n == 3 {
|
||||
radius_x /= (begin_angle + 2.0 / 3.0 * PI).cos();
|
||||
radius_x /= (begin_angle + 2.0 / 3.0 * PI).sin();
|
||||
}
|
||||
if n == 4 {
|
||||
radius_x /= begin_angle.sin();
|
||||
radius_y /= begin_angle.cos();
|
||||
}
|
||||
|
||||
// amount of radians between each corner if it were on a circle
|
||||
let angle_increment = PI * 2.0 / n as f32;
|
||||
|
||||
// start one corner after the starting point because we draw lines
|
||||
let mut current_angle = angle_increment + begin_angle;
|
||||
|
||||
// center
|
||||
let fx = draw.loc.x as f32;
|
||||
let fy = draw.loc.y as f32;
|
||||
let mut last_x = begin_angle.cos() * radius_x;
|
||||
let mut last_y = begin_angle.sin() * radius_y;
|
||||
|
||||
let mut last_x = begin_angle.sin() * radius_x;
|
||||
let mut last_y = begin_angle.cos() * radius_y;
|
||||
// loop over corners and draw a line from the last to the current
|
||||
for _ in 0..n {
|
||||
let new_x = current_angle.cos() * radius_x;
|
||||
let new_y = current_angle.sin() * radius_y;
|
||||
let new_x = current_angle.sin() * radius_x;
|
||||
let new_y = current_angle.cos() * radius_y;
|
||||
|
||||
self.draw_line(
|
||||
draw.at((fx + last_x) as usize, (fy + last_y) as usize),
|
||||
draw.at((fx + new_x) as usize, (fy + new_y) as usize),
|
||||
|
|
|
|||
29
src/fill.rs
29
src/fill.rs
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
App,
|
||||
};
|
||||
|
||||
/// The state struct for the fill algorithm
|
||||
struct Filler<'app> {
|
||||
col: Color32,
|
||||
open: Vec<Location>,
|
||||
|
|
@ -18,9 +19,10 @@ struct Filler<'app> {
|
|||
impl<'app> Filler<'app> {
|
||||
fn new(app: &'app mut App, draw: DrawParams) -> Self {
|
||||
Self {
|
||||
col: app.image[[draw.loc.x, draw.loc.y]],
|
||||
col: app.image[[draw.loc.x, draw.loc.y]], // color to replace
|
||||
open: vec![draw.loc],
|
||||
closed: HashSet::with_capacity(128),
|
||||
// color to fill with
|
||||
fill_color: Color32::from_rgb(
|
||||
(draw.px >> 16) as u8,
|
||||
(draw.px >> 8) as u8,
|
||||
|
|
@ -31,32 +33,41 @@ impl<'app> Filler<'app> {
|
|||
}
|
||||
|
||||
fn push(&mut self, node: Location) {
|
||||
// has this been visited yet?
|
||||
if !self.closed.contains(&node) {
|
||||
self.open.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Fills the area.
|
||||
// 1. add current pixel to open list
|
||||
// 2. pop first pixel from open list
|
||||
// 3. add it to closed list
|
||||
// 4. check if it is valid
|
||||
// 5. color it
|
||||
// 6. add its neighbors to the open list
|
||||
// 7. repeat 2 until open list is empty
|
||||
/// Fills the area.
|
||||
///
|
||||
/// "nodes" are pixels
|
||||
/// 1. add current pixel to open list
|
||||
/// 2. pop first pixel from open list
|
||||
/// 3. add it to closed list
|
||||
/// 4. check if it is valid
|
||||
/// 5. color it
|
||||
/// 6. add its neighbors to the open list
|
||||
/// 7. repeat from 2 on until open list is empty
|
||||
fn fill(&mut self) {
|
||||
let img_size = self.app.image.size();
|
||||
// until all nodes are visited
|
||||
while let Some(node) = self.open.pop() {
|
||||
// mark node done
|
||||
self.closed.insert(node);
|
||||
// if node is invalid, skip it
|
||||
if node.x >= img_size[0] || node.y >= img_size[1] {
|
||||
continue;
|
||||
}
|
||||
// if node is wrong color, skip it
|
||||
if self.app.image[[node.x, node.y]] != self.col {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set color without bounds check (already done above)
|
||||
self.app.set_px_unchecked(node.x, node.y, self.fill_color);
|
||||
|
||||
// mark neighbors to be visited
|
||||
self.push(node.offset(-1, 0));
|
||||
self.push(node.offset(1, 0));
|
||||
self.push(node.offset(0, -1));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::App;
|
|||
|
||||
impl App {
|
||||
/// SAFETY: Call only when self.filename is present
|
||||
/// loads a file from disk (called after open dialog is confirmed)
|
||||
pub fn load(&mut self) {
|
||||
if let Ok(x) = ImageReader::open(self.filename.as_ref().unwrap())
|
||||
.expect("This file can't be opened due to an IO error")
|
||||
|
|
@ -30,6 +31,7 @@ impl App {
|
|||
}
|
||||
|
||||
/// SAFETY: Call only when self.filename is present
|
||||
/// saves the image to disk
|
||||
pub fn save(&mut self) {
|
||||
let size = self.image.size();
|
||||
DynamicImage::ImageRgb8(ImageBuffer::from_fn(
|
||||
|
|
|
|||
25
src/main.rs
25
src/main.rs
|
|
@ -84,8 +84,12 @@ impl App {
|
|||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
|
||||
ctx.request_repaint_after(Duration::from_millis(0));
|
||||
// try to do 90fps
|
||||
ctx.request_repaint_after(Duration::from_millis(1000 / 90));
|
||||
|
||||
self.handle_dialogs(ctx);
|
||||
|
||||
// the content frame
|
||||
let f = Frame::none()
|
||||
.inner_margin(Margin::same(2.0))
|
||||
.fill(Color32::from_rgb(0x00, 0x00, 0x00));
|
||||
|
|
@ -153,20 +157,27 @@ impl eframe::App for App {
|
|||
[size.x as usize, size.y as usize],
|
||||
);
|
||||
self.image_to_texture(&mut ctx.tex_manager().write());
|
||||
|
||||
// draw the texture
|
||||
let r = ui.add(Image::from_texture(SizedTexture::new(self.tex, size)));
|
||||
|
||||
// handle mouse and keyboard input
|
||||
// handle keyboard and mouse input
|
||||
ui.input(|inp| {
|
||||
// get pointer pos offset to be in the image or return if its not inside the window
|
||||
let Some(pointer_pos) = r.hover_pos().map(|pos| pos - r.rect.min) else {
|
||||
return;
|
||||
};
|
||||
// return if not actually on the image
|
||||
if !r.hovered() {
|
||||
return; // we don't need to handle it if it's not in focus
|
||||
}
|
||||
let Some(pointer_pos) = r.hover_pos().map(|x| x - r.rect.min) else {
|
||||
return; // we don't need to handle it if the image is not under the cursor
|
||||
};
|
||||
|
||||
// handle pulling shapes
|
||||
if inp.pointer.secondary_down() || self.pull_start.is_some() {
|
||||
self.pull(&inp, [pointer_pos.x as usize, pointer_pos.y as usize]);
|
||||
self.pull(inp, [pointer_pos.x as usize, pointer_pos.y as usize]);
|
||||
return;
|
||||
}
|
||||
|
||||
if inp.key_down(Key::D) {
|
||||
self.draw_ngon(
|
||||
self.draw.at(pointer_pos.x as usize, pointer_pos.y as usize),
|
||||
|
|
@ -198,8 +209,10 @@ impl eframe::App for App {
|
|||
if inp.pointer.primary_down() {
|
||||
let draw = self.draw.at(pointer_pos.x as usize, pointer_pos.y as usize);
|
||||
if self.mode.run_once() {
|
||||
// don't interpolate
|
||||
self.mode.into_fn()(self, draw);
|
||||
} else {
|
||||
// interpolate
|
||||
self.draw_mouse(draw, self.mode.into_fn());
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ impl Mode {
|
|||
ui.radio_value(&mut app.mode, Fill, "Fill");
|
||||
}
|
||||
|
||||
/// Some things shouldn't be interpolated and only run once
|
||||
pub fn run_once(self) -> bool {
|
||||
#[allow(clippy::match_like_matches_macro)] // this may have more added later
|
||||
match self {
|
||||
Fill => true,
|
||||
_ => false,
|
||||
|
|
@ -32,7 +34,7 @@ impl Mode {
|
|||
/// universalizes the mode into a single function with pre-set size
|
||||
pub fn into_fn_sized(self, radius_x: f32, radius_y: f32) -> fn(&mut App, DrawParams) {
|
||||
static mut RADIUS: (f32, f32) = (0.0, 0.0);
|
||||
// this is single-threaded
|
||||
// this is single-threaded so a static is fine
|
||||
unsafe {
|
||||
RADIUS = (radius_x, radius_y); // need this for compatible match arms
|
||||
match self {
|
||||
|
|
|
|||
17
src/pull.rs
17
src/pull.rs
|
|
@ -7,6 +7,7 @@ impl App {
|
|||
self.real_image = self.image.clone();
|
||||
}
|
||||
|
||||
// not perfectly efficient, but fast enough to be responsive
|
||||
pub fn pull(&mut self, inp: &InputState, pointer_pos: [usize; 2]) {
|
||||
if let Some(pull_start) = self.pull_start {
|
||||
// clone the image to reset it, then draw the current state of the pulled brush
|
||||
|
|
@ -19,10 +20,10 @@ impl App {
|
|||
));
|
||||
|
||||
// the distance pulled divided by two (-> the radius)
|
||||
let pull_x = (pull_start[0] as isize - pointer_pos[0] as isize) / 2;
|
||||
let pull_y = (pull_start[1] as isize - pointer_pos[1] as isize) / 2;
|
||||
let pull_x = (pointer_pos[0] as isize - pull_start[0] as isize) / 2;
|
||||
let pull_y = (pointer_pos[1] as isize - pull_start[1] as isize) / 2;
|
||||
let pull_size = if inp.modifiers.shift {
|
||||
// if shift is pressed, both sizes are the same
|
||||
// if shift is pressed, both sizes are the same (specifically, the biggest of the two)
|
||||
#[inline]
|
||||
fn sign(x: isize) -> isize {
|
||||
if x < 0 {
|
||||
|
|
@ -37,23 +38,25 @@ impl App {
|
|||
} else {
|
||||
[pull_x, pull_y]
|
||||
};
|
||||
|
||||
// draw:
|
||||
self.mode
|
||||
.into_fn_sized(pull_size[0] as f32, pull_size[1] as f32)(
|
||||
self,
|
||||
self.draw.at(
|
||||
// this is going to be the center
|
||||
(pull_start[0] as isize - pull_size[0]) as usize,
|
||||
(pull_start[1] as isize - pull_size[1]) as usize,
|
||||
(pull_start[0] as isize + pull_size[0]) as usize,
|
||||
(pull_start[1] as isize + pull_size[1]) as usize,
|
||||
),
|
||||
);
|
||||
|
||||
// resets the pull
|
||||
// reset and save the pull if user has stopped pulling
|
||||
if !inp.pointer.secondary_down() {
|
||||
self.pull_start = None;
|
||||
self.sync();
|
||||
}
|
||||
} else {
|
||||
// starts a pull
|
||||
// start a pull
|
||||
self.pull_start = Some(pointer_pos);
|
||||
self.sync();
|
||||
}
|
||||
|
|
|
|||
11
src/tex.rs
11
src/tex.rs
|
|
@ -7,12 +7,15 @@ use micro_ndarray::Array;
|
|||
use crate::{compress::FlatArea, App};
|
||||
|
||||
impl App {
|
||||
/// changes the image size when resizing window
|
||||
pub fn correct_tex_size(&mut self, texman: &mut TextureManager, window_size: [usize; 2]) {
|
||||
if self.image.size() == window_size {
|
||||
return;
|
||||
}
|
||||
// creates a new image and transfers the pixels
|
||||
let mut new_image = Array::new_with([window_size[0], window_size[1]], Color32::WHITE);
|
||||
for (pos, pixel) in self.image.iter() {
|
||||
// if its within the image, set it
|
||||
if let Some(px) = new_image.get_mut(pos) {
|
||||
*px = *pixel;
|
||||
}
|
||||
|
|
@ -25,11 +28,12 @@ impl App {
|
|||
Pos2::new(window_size[0] as f32, window_size[1] as f32),
|
||||
));
|
||||
|
||||
// create a renderable texture from the new image
|
||||
let cimg = ColorImage {
|
||||
size: self.image.size(),
|
||||
pixels: self.image.as_flattened().to_vec(),
|
||||
};
|
||||
texman.free(self.tex);
|
||||
texman.free(self.tex); // drop old texture
|
||||
self.tex = texman.alloc(
|
||||
"canvas".to_owned(),
|
||||
ImageData::Color(Arc::new(cimg)),
|
||||
|
|
@ -38,11 +42,13 @@ impl App {
|
|||
minification: TextureFilter::Linear,
|
||||
},
|
||||
);
|
||||
// write image into the texture
|
||||
self.image_to_texture(texman);
|
||||
}
|
||||
|
||||
pub fn image_to_texture(&mut self, texman: &mut TextureManager) {
|
||||
let changes = self.changes.take();
|
||||
// if its so few it can be updated in single pixels
|
||||
if let Some(changelist) = changes.changelist {
|
||||
for change in changelist {
|
||||
let cimg = ColorImage {
|
||||
|
|
@ -63,10 +69,11 @@ impl App {
|
|||
}
|
||||
return;
|
||||
}
|
||||
// if its only a region
|
||||
if changes.area < self.image.as_flattened().len() / 2 {
|
||||
let cimg = ColorImage {
|
||||
size: changes.size,
|
||||
pixels: self.image.area_flat(changes.min, changes.size),
|
||||
pixels: self.image.area_flat(changes.min, changes.size), // only an area of the image, as a flattened array of pixels
|
||||
};
|
||||
texman.set(
|
||||
self.tex,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue