Skip to content

Commit

Permalink
Implement drawpile-cmd and drawpile-timelapse
Browse files Browse the repository at this point in the history
Command-line tools. The former should work mostly like it did in
Drawpile 2.1. The latter is a new tool that turns recordings into
timelapse videos (via ffmpeg.)
  • Loading branch information
askmeaboutlo0m committed Sep 17, 2023
1 parent 7fae6d6 commit ce590ee
Show file tree
Hide file tree
Showing 25 changed files with 2,108 additions and 49 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [
"src/drawdance/rust",
"src/drawdance/libmsg",
"src/tools/dprectool",
"src/tools/drawpile-cmd",
"src/tools/drawpile-timelapse",
]

[workspace.package]
Expand Down
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Unreleased Version 2.2.0-pre
* Feature: Let operators create layers beyond the 256 per-user maximum. They will use layers of user 0 first, then 255, 254 etc. Thanks to haxekhaex2 for reporting.
* Fix: Make Drawpile 2.1 binary (dprec) recordings play back properly. Text (dptxt) recordings are not supported.
* Fix: Synchronize rendering during recording playback properly.
* Feature: Bring back drawpile-cmd, the command-line tool that renders Drawpile recordings to images. Should also mostly work like it did in Drawpile 2.1.
* Feature: Implement drawpile-timelapse, a new command-line tool that turns Drawpile recordings into timelapse videos.

2023-08-26 Version 2.2.0-beta.7
* Fix: Make classic brushes not go brighter when smudging into transparency. Thanks to cada for reporting.
Expand Down
18 changes: 18 additions & 0 deletions pkg/custom-apprun.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ case "${1-}" in
exec=usr/bin/dprectool
shift
;;
--drawpile-cmd)
exec=usr/bin/drawpile-cmd
shift
;;
--drawpile-timelapse)
exec=usr/bin/drawpile-timelapse
shift
;;
--help)
echo "Drawpile @GIT_VERSION@"
echo
Expand All @@ -31,6 +39,16 @@ case "${1-}" in
echo " Runs dprectool, a command-line tool for"
echo " interconverting Drawpile recordings."
fi
if [ -x "$this_dir"/usr/bin/drawpile-cmd ]; then
echo "--drawpile-cmd"
echo " Runs drawpile-cmd, a command-line tool for"
echo " rendering Drawpile recordings to images."
fi
if [ -x "$this_dir"/usr/bin/drawpile-timelapse ]; then
echo "--drawpile-timelapse"
echo " Runs drawpile-timelapse, a command-line tool for"
echo " turning Drawpile recordings into timelapse videos."
fi
exit 0
;;
esac
Expand Down
47 changes: 7 additions & 40 deletions src/drawdance/libengine/dpengine/image_transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@
* If not otherwise noted, this code is wholly based on the Qt framework's
* raster paint engine implementation, using it under the GNU General Public
* License, version 3. See 3rdparty/licenses/qt/license.GPL3 for details.
*
* --------------------------------------------------------------------
*
* Parts of this code are based on Krita, using it under the GNU General
* Public License, version 3. See 3rdparty/licenses/krita/COPYING.txt for
* details.
*/
#include "image_transform.h"
#include "dpcommon/conversions.h"
Expand Down Expand Up @@ -169,40 +163,13 @@ static DP_Pixel8 *fetch_transformed_pixels(int width, int height,
return out_buffer;
}

unsigned int get_span_opacity(int interpolation, int coverage)
static uint8_t get_span_opacity(int interpolation, int coverage)
{
switch (interpolation) {
case DP_MSG_TRANSFORM_REGION_MODE_NEAREST:
return coverage < 128 ? 0u : 255u;
default:
return DP_int_to_uint(CLAMP(coverage, 0, 255));
}
}

// Multiplying two bytes as if they were floats between 0 and 1.
// Adapted from Krita, see license above.
static uint8_t mul(unsigned int a, unsigned int b)
{
unsigned int c = a * b + 0x80u;
return DP_uint_to_uint8(((c >> 8u) + c) >> 8u);
}

// Normal blending with 8 bit pixels.
static void process_span(int len, unsigned int opacity,
DP_Pixel8 *DP_RESTRICT src, DP_Pixel8 *DP_RESTRICT dst)
{
for (int i = 0; i < len; ++i) {
DP_Pixel8 s = src[i];
DP_Pixel8 d = dst[i];
unsigned int sa1 = 255u - mul(s.a, opacity);
if (sa1 != 255u) {
dst[i] = (DP_Pixel8){
.b = (uint8_t)(mul(s.b, opacity) + mul(d.b, sa1)),
.g = (uint8_t)(mul(s.g, opacity) + mul(d.g, sa1)),
.r = (uint8_t)(mul(s.r, opacity) + mul(d.r, sa1)),
.a = (uint8_t)(mul(s.a, opacity) + mul(d.a, sa1)),
};
}
return DP_int_to_uint8(CLAMP(coverage, 0, 255));
}
}

Expand Down Expand Up @@ -252,8 +219,8 @@ static void render_spans(int count, const DP_FT_Span *spans, void *user)

int pr = spans->x + spans->len;
int pl = DP_min_int(l, pr - x);
process_span(pl, get_span_opacity(interpolation, coverage),
src + offset, dst + offset);
DP_blend_pixels8(dst + offset, src + offset, pl,
get_span_opacity(interpolation, coverage));

l -= pl;
x += pl;
Expand All @@ -277,9 +244,9 @@ static DP_FT_Vector transform_outline_point(DP_Transform tf, double x, double y)
}

bool DP_image_transform_draw(int src_width, int src_height,
const DP_Pixel8 *src_pixels,
DP_DrawContext *dc, DP_Image *dst_img,
DP_Transform tf, int interpolation)
const DP_Pixel8 *src_pixels, DP_DrawContext *dc,
DP_Image *dst_img, DP_Transform tf,
int interpolation)
{
DP_Transform delta = DP_transform_make(1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
1.0 / 65536.0, 1.0 / 65536.0, 1.0);
Expand Down
53 changes: 53 additions & 0 deletions src/drawdance/libengine/dpengine/pixels.c
Original file line number Diff line number Diff line change
Expand Up @@ -2127,3 +2127,56 @@ void DP_posterize_mask(DP_Pixel15 *dst, int posterize_num, const uint16_t *mask,
*dst = from_ubgra(posterize(p, o, DP_pixel15_unpremultiply(*dst)));
});
}


// SPDX-SnippetBegin
// SPDX-License-Identifier: GPL-3.0-or-later
// SDPX—SnippetName: 8 bit multiplication adapted from Krita
static uint8_t mul(unsigned int a, unsigned int b)
{
unsigned int c = a * b + 0x80u;
return DP_uint_to_uint8(((c >> 8u) + c) >> 8u);
}
// SPDX-SnippetEnd

void DP_blend_color8_to(DP_Pixel8 *DP_RESTRICT out,
const DP_Pixel8 *DP_RESTRICT dst, DP_UPixel8 color,
int pixel_count, uint8_t opacity)
{
DP_Pixel8 src = DP_pixel8_premultiply(color);
unsigned int sa1 = 255u - mul(src.a, opacity);
if (sa1 != 255u) {
unsigned int sb = mul(src.b, opacity);
unsigned int sg = mul(src.g, opacity);
unsigned int sr = mul(src.r, opacity);
unsigned int sa = mul(src.a, opacity);
for (int i = 0; i < pixel_count; ++i) {
DP_Pixel8 d = dst[i];
out[i] = (DP_Pixel8){
.b = (uint8_t)(sb + mul(d.b, sa1)),
.g = (uint8_t)(sg + mul(d.g, sa1)),
.r = (uint8_t)(sr + mul(d.r, sa1)),
.a = (uint8_t)(sa + mul(d.a, sa1)),
};
}
}
}

void DP_blend_pixels8(DP_Pixel8 *DP_RESTRICT dst,
const DP_Pixel8 *DP_RESTRICT src, int pixel_count,
uint8_t opacity)
{
for (int i = 0; i < pixel_count; ++i) {
DP_Pixel8 s = src[i];
DP_Pixel8 d = dst[i];
unsigned int sa1 = 255u - mul(s.a, opacity);
if (sa1 != 255u) {
dst[i] = (DP_Pixel8){
.b = (uint8_t)(mul(s.b, opacity) + mul(d.b, sa1)),
.g = (uint8_t)(mul(s.g, opacity) + mul(d.g, sa1)),
.r = (uint8_t)(mul(s.r, opacity) + mul(d.r, sa1)),
.a = (uint8_t)(mul(s.a, opacity) + mul(d.a, sa1)),
};
}
}
}
11 changes: 11 additions & 0 deletions src/drawdance/libengine/dpengine/pixels.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,15 @@ void DP_posterize_mask(DP_Pixel15 *dst, int posterize_num, const uint16_t *mask,
int base_skip);


// 8 bit pixel Normal blending.

void DP_blend_color8_to(DP_Pixel8 *DP_RESTRICT out,
const DP_Pixel8 *DP_RESTRICT dst, DP_UPixel8 color,
int pixel_count, uint8_t opacity);

void DP_blend_pixels8(DP_Pixel8 *DP_RESTRICT dst,
const DP_Pixel8 *DP_RESTRICT src, int pixel_count,
uint8_t opacity);


#endif
126 changes: 118 additions & 8 deletions src/drawdance/rust/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* automatically generated by rust-bindgen 0.66.1 */
/* automatically generated by rust-bindgen 0.68.1 */

pub const DP_SIMD_ALIGNMENT: u32 = 32;
pub const DP_DRAW_CONTEXT_STAMP_MAX_DIAMETER: u32 = 260;
pub const DP_DRAW_CONTEXT_STAMP_BUFFER_SIZE: u32 = 67600;
pub const DP_DRAW_CONTEXT_TRANSFORM_BUFFER_SIZE: u32 = 204;
Expand Down Expand Up @@ -342,12 +341,6 @@ extern "C" {
extern "C" {
pub fn DP_free(ptr: *mut ::std::os::raw::c_void);
}
extern "C" {
pub fn DP_malloc_simd(size: usize) -> *mut ::std::os::raw::c_void;
}
extern "C" {
pub fn DP_malloc_simd_zeroed(size: usize) -> *mut ::std::os::raw::c_void;
}
extern "C" {
pub fn DP_vformat(
fmt: *const ::std::os::raw::c_char,
Expand Down Expand Up @@ -1916,6 +1909,23 @@ extern "C" {
base_skip: ::std::os::raw::c_int,
);
}
extern "C" {
pub fn DP_blend_color8_to(
out: *mut DP_Pixel8,
dst: *const DP_Pixel8,
color: DP_UPixel8,
pixel_count: ::std::os::raw::c_int,
opacity: u8,
);
}
extern "C" {
pub fn DP_blend_pixels8(
dst: *mut DP_Pixel8,
src: *const DP_Pixel8,
pixel_count: ::std::os::raw::c_int,
opacity: u8,
);
}
pub const DP_IMAGE_FILE_TYPE_GUESS: DP_ImageFileType = 0;
pub const DP_IMAGE_FILE_TYPE_PNG: DP_ImageFileType = 1;
pub const DP_IMAGE_FILE_TYPE_JPEG: DP_ImageFileType = 2;
Expand Down Expand Up @@ -4015,6 +4025,7 @@ extern "C" {
out_lle: *mut *mut DP_LayerListEntry,
out_lp: *mut *mut DP_LayerProps,
out_os: *mut *const DP_OnionSkin,
out_parent_opacity: *mut u16,
) -> DP_ViewModeContext;
}
extern "C" {
Expand Down Expand Up @@ -5149,6 +5160,99 @@ extern "C" {
extern "C" {
pub fn DP_paint_engine_sample_canvas_state_inc(pe: *mut DP_PaintEngine) -> *mut DP_CanvasState;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DP_SaveFormat {
pub title: *const ::std::os::raw::c_char,
pub extensions: *mut *const ::std::os::raw::c_char,
}
#[test]
fn bindgen_test_layout_DP_SaveFormat() {
const UNINIT: ::std::mem::MaybeUninit<DP_SaveFormat> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<DP_SaveFormat>(),
16usize,
concat!("Size of: ", stringify!(DP_SaveFormat))
);
assert_eq!(
::std::mem::align_of::<DP_SaveFormat>(),
8usize,
concat!("Alignment of ", stringify!(DP_SaveFormat))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).title) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(DP_SaveFormat),
"::",
stringify!(title)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).extensions) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(DP_SaveFormat),
"::",
stringify!(extensions)
)
);
}
extern "C" {
pub fn DP_save_supported_formats() -> *const DP_SaveFormat;
}
pub const DP_SAVE_IMAGE_GUESS: DP_SaveImageType = 0;
pub const DP_SAVE_IMAGE_ORA: DP_SaveImageType = 1;
pub const DP_SAVE_IMAGE_PNG: DP_SaveImageType = 2;
pub const DP_SAVE_IMAGE_JPEG: DP_SaveImageType = 3;
pub type DP_SaveImageType = ::std::os::raw::c_uint;
pub const DP_SAVE_RESULT_SUCCESS: DP_SaveResult = 0;
pub const DP_SAVE_RESULT_BAD_ARGUMENTS: DP_SaveResult = 1;
pub const DP_SAVE_RESULT_NO_EXTENSION: DP_SaveResult = 2;
pub const DP_SAVE_RESULT_UNKNOWN_FORMAT: DP_SaveResult = 3;
pub const DP_SAVE_RESULT_FLATTEN_ERROR: DP_SaveResult = 4;
pub const DP_SAVE_RESULT_OPEN_ERROR: DP_SaveResult = 5;
pub const DP_SAVE_RESULT_WRITE_ERROR: DP_SaveResult = 6;
pub const DP_SAVE_RESULT_INTERNAL_ERROR: DP_SaveResult = 7;
pub const DP_SAVE_RESULT_CANCEL: DP_SaveResult = 8;
pub type DP_SaveResult = ::std::os::raw::c_uint;
extern "C" {
pub fn DP_save(
cs: *mut DP_CanvasState,
dc: *mut DP_DrawContext,
type_: DP_SaveImageType,
path: *const ::std::os::raw::c_char,
) -> DP_SaveResult;
}
pub type DP_SaveAnimationProgressFn = ::std::option::Option<
unsafe extern "C" fn(user: *mut ::std::os::raw::c_void, progress: f64) -> bool,
>;
extern "C" {
pub fn DP_save_animation_frames(
cs: *mut DP_CanvasState,
path: *const ::std::os::raw::c_char,
crop: *mut DP_Rect,
start: ::std::os::raw::c_int,
end_inclusive: ::std::os::raw::c_int,
progress_fn: DP_SaveAnimationProgressFn,
user: *mut ::std::os::raw::c_void,
) -> DP_SaveResult;
}
extern "C" {
pub fn DP_save_animation_gif(
cs: *mut DP_CanvasState,
path: *const ::std::os::raw::c_char,
crop: *mut DP_Rect,
start: ::std::os::raw::c_int,
end_inclusive: ::std::os::raw::c_int,
framerate: ::std::os::raw::c_int,
progress_fn: DP_SaveAnimationProgressFn,
user: *mut ::std::os::raw::c_void,
) -> DP_SaveResult;
}
pub const DP_ACCESS_TIER_OPERATOR: DP_AccessTier = 0;
pub const DP_ACCESS_TIER_TRUSTED: DP_AccessTier = 1;
pub const DP_ACCESS_TIER_AUTHENTICATED: DP_AccessTier = 2;
Expand Down Expand Up @@ -8572,6 +8676,12 @@ extern "C" {
bufsize: usize,
) -> *mut DP_Message;
}
extern "C" {
pub fn DP_message_compat_flag_indirect(msg: *mut DP_Message) -> bool;
}
extern "C" {
pub fn DP_message_compat_flag_indirect_set(msg: *mut DP_Message);
}
extern "C" {
pub fn DP_msg_draw_dabs_classic_indirect(mddc: *mut DP_MsgDrawDabsClassic) -> bool;
}
Expand Down
Loading

0 comments on commit ce590ee

Please sign in to comment.