Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

screenshot: add screenshot-to-clipboard command #15568

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DOCS/man/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,12 @@ Screenshot Commands
Like all input command parameters, the filename is subject to property
expansion as described in `Property Expansion`_.

``screenshot-to-clipboard [<flags>]``
Take a screenshot and save it to the system clipboard.

The ``flags`` argument is like the first argument to ``screenshot`` and
supports ``subtitles``, ``video``, ``window``.

``screenshot-raw [<flags>]``
Return a screenshot in memory. This can be used only through the client
API. The MPV_FORMAT_NODE_MAP returned by this command has the ``w``, ``h``,
Expand Down
3 changes: 3 additions & 0 deletions osdep/mac/app_bridge_objc.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>

#include <libswscale/swscale.h>

#include "player/client.h"
#include "video/out/libmpv.h"
#include "libmpv/render_gl.h"
Expand All @@ -30,6 +32,7 @@
#include "input/event.h"
#include "input/keycodes.h"
#include "video/out/win_state.h"
#include "video/sws_utils.h"

#include "osdep/main-fn.h"
#include "osdep/mac/app_bridge.h"
Expand Down
233 changes: 232 additions & 1 deletion osdep/mac/clipboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import Cocoa
class Clipboard: NSObject {
var pasteboard: NSPasteboard { return NSPasteboard.general }
var changeCount: Int = 0
var ctx: UnsafeMutablePointer<clipboard_ctx>

@objc override init() {
@objc init(context: UnsafeMutablePointer<clipboard_ctx>) {
ctx = context
super.init()
changeCount = pasteboard.changeCount
}
Expand All @@ -41,6 +43,7 @@ class Clipboard: NSObject {
data: UnsafeMutablePointer<clipboard_data>) -> clipboard_result {
switch (params.pointee.type, data.pointee.type) {
case (CLIPBOARD_DATA_TEXT, CLIPBOARD_DATA_TEXT): return setString(data)
case (CLIPBOARD_DATA_IMAGE, CLIPBOARD_DATA_IMAGE): return setImage(data)
default: break
}

Expand Down Expand Up @@ -73,4 +76,232 @@ class Clipboard: NSObject {

return CLIPBOARD_SUCCESS
}

private func getColorspaceName(_ plcsp: pl_color_space, gray: Bool) -> CFString? {
if gray {
if plcsp.transfer == PL_COLOR_TRC_LINEAR {
return CGColorSpace.linearGray
} else if plcsp.transfer == PL_COLOR_TRC_GAMMA22 {
return CGColorSpace.genericGrayGamma2_2
}
} else {
switch plcsp.primaries {
case PL_COLOR_PRIM_DISPLAY_P3:
if plcsp.transfer == PL_COLOR_TRC_BT_1886 {
return CGColorSpace.displayP3
} else if plcsp.transfer == PL_COLOR_TRC_HLG {
return CGColorSpace.displayP3_HLG
}
case PL_COLOR_PRIM_BT_709:
if plcsp.transfer == PL_COLOR_TRC_LINEAR {
return CGColorSpace.linearSRGB
} else if plcsp.transfer == PL_COLOR_TRC_BT_1886 {
return CGColorSpace.itur_709
} else if plcsp.transfer == PL_COLOR_TRC_SRGB {
return CGColorSpace.sRGB
}
case PL_COLOR_PRIM_DCI_P3:
if plcsp.transfer == PL_COLOR_TRC_BT_1886 {
return CGColorSpace.dcip3
}
case PL_COLOR_PRIM_BT_2020:
if plcsp.transfer == PL_COLOR_TRC_BT_1886 {
return CGColorSpace.itur_2020
}
case PL_COLOR_PRIM_ADOBE:
return CGColorSpace.adobeRGB1998
case PL_COLOR_PRIM_APPLE:
if plcsp.transfer == PL_COLOR_TRC_LINEAR {
return CGColorSpace.genericRGBLinear
}
default:
break
}
}

return nil
}

private func convertIntoRep(_ rep: NSBitmapImageRep, imgfmt: Int32, plcsp: pl_color_space, bps: Int32, image: mp_image) -> Bool {
var image = image
var dest = mp_image()
mp_image_setfmt(&dest, imgfmt)
mp_image_set_size(&dest, image.w, image.h)

let planes = UnsafeMutablePointer<UnsafeMutablePointer<UInt8>?>.allocate(capacity: 5)
rep.getBitmapDataPlanes(planes)

if !withUnsafeMutableBytes(of: &dest.stride, { (stridePtr) -> Bool in
return withUnsafeMutableBytes(of: &dest.planes) { (planesPtr) -> Bool in
guard let destStrides = stridePtr.baseAddress?.assumingMemoryBound(to: type(of: image.stride.0)) else {
return false
}
guard let destPlanes = planesPtr.baseAddress?.assumingMemoryBound(to: type(of: image.planes.0)) else {
return false
}

for i in 0..<Int(MP_MAX_PLANES) {
destPlanes[i] = planes[i]
destStrides[i] = Int32(rep.bytesPerRow)
}

return true
}
}) {
assert(false, "Binding pointer to stride or planes array failed; this should be impossible")
return false
}

dest.params.repr.sys = PL_COLOR_SYSTEM_RGB
dest.params.repr.levels = PL_COLOR_LEVELS_FULL
dest.params.repr.alpha = rep.hasAlpha ? (rep.bitmapFormat.contains(.alphaNonpremultiplied) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_PREMULTIPLIED) : PL_ALPHA_UNKNOWN
dest.params.repr.bits.sample_depth = bps
dest.params.repr.bits.color_depth = bps

dest.params.color = plcsp

return mp_image_swscale(&dest, &image, ctx.pointee.global, ctx.pointee.log) >= 0
}

private func createImageRep(_ image: mp_image) -> NSBitmapImageRep? {
// Need it to nominally be mutable to pass to C functions later
var image = image
var imgfmt = image.imgfmt

var compatible = true
switch imgfmt {
case IMGFMT_YAP8, IMGFMT_YAP16, IMGFMT_Y8, IMGFMT_Y16, IMGFMT_ARGB, IMGFMT_RGBA, IMGFMT_RGB0, IMGFMT_RGBA64:
break
default:
compatible = false
}

if image.params.repr.levels != PL_COLOR_LEVELS_FULL {
compatible = false
}

if image.num_planes > 5 {
return nil
}

let planes = UnsafeMutablePointer<UnsafeMutablePointer<UInt8>?>.allocate(capacity: 5)
planes.initialize(repeating: nil, count: 5)

var bps = image.fmt.comps.0.size
var spp = mp_imgfmt_desc_get_num_comps(&image.fmt)
let alpha = (image.fmt.flags & MP_IMGFLAG_ALPHA) != 0
let gray = (image.fmt.flags & MP_IMGFLAG_GRAY) != 0
let csp: NSColorSpaceName = gray ? .calibratedWhite : .calibratedRGB
var formatFlags: NSBitmapImageRep.Format = []
if alpha && image.fmt.comps.3.plane == 0 && image.fmt.comps.3.offset == 0 {
formatFlags.insert(.alphaFirst)
}
if image.params.repr.alpha == PL_ALPHA_INDEPENDENT {
formatFlags.insert(.alphaNonpremultiplied)
}
var bpp = image.fmt.bpp.0
var bytesPerRow = image.stride.0
var planar = image.num_planes > 1

if !withUnsafeBytes(of: &image.stride, { (rawPtr) -> Bool in
let ptr = rawPtr.baseAddress!.assumingMemoryBound(to: type(of: image.stride.0))
for i in 0..<Int(image.num_planes) where ptr[i] != bytesPerRow {
return false
}
return true
}) {
compatible = false
}

if compatible {
withUnsafeBytes(of: &image.planes) { (rawPtr) in
let ptr = rawPtr.baseAddress!.assumingMemoryBound(to: type(of: image.planes.0))
for i in 0..<Int(image.num_planes) {
planes[i] = ptr[i]
}
}

if bpp == 24 {
bpp = 32
}
} else {
bps = bps <= 8 ? 8 : 16
formatFlags.remove(.alphaFirst)
if gray {
if bps > 8 {
imgfmt = alpha ? IMGFMT_YAP16 : IMGFMT_Y16
bpp = 16
} else {
imgfmt = alpha ? IMGFMT_YAP8 : IMGFMT_Y8
bpp = 8
}
} else {
if bps > 8 {
imgfmt = IMGFMT_RGBA64
bpp = 64
} else {
imgfmt = alpha ? IMGFMT_RGBA : IMGFMT_RGB0
bpp = 32
}
}

bytesPerRow = 0
planar = (gray && alpha)
spp = (gray ? 1 : 3) + (alpha ? 1 : 0)
}

guard let rep = NSBitmapImageRep(bitmapDataPlanes: planes,
pixelsWide: Int(image.w),
pixelsHigh: Int(image.h),
bitsPerSample: Int(bps),
samplesPerPixel: Int(spp),
hasAlpha: alpha,
isPlanar: planar,
colorSpaceName: csp,
bitmapFormat: formatFlags,
bytesPerRow: Int(bytesPerRow),
bitsPerPixel: Int(bpp)) else {
return nil
}

var plcsp = image.params.color
let cgSpaceName = getColorspaceName(plcsp, gray: gray)

if cgSpaceName == nil {
compatible = false
plcsp.primaries = PL_COLOR_PRIM_BT_709
plcsp.transfer = PL_COLOR_TRC_SRGB
}

guard let nscsp = (!gray && image.icc_profile != nil) ?
NSColorSpace(iccProfileData: Data(bytes: image.icc_profile.pointee.data, count: image.icc_profile.pointee.size)) :
(CGColorSpace(name: cgSpaceName ?? CGColorSpace.sRGB).flatMap { NSColorSpace(cgColorSpace: $0) }) else {
return nil
}

guard let rep = rep.retagging(with: nscsp) else {
return nil
}

if !compatible && !convertIntoRep(rep, imgfmt: Int32(imgfmt.rawValue), plcsp: plcsp, bps: Int32(bps), image: image) {
return nil
}

return rep
}

func setImage(_ data: UnsafeMutablePointer<clipboard_data>) -> clipboard_result {
guard let rep = createImageRep(data.pointee.u.image.pointee) else {
return CLIPBOARD_FAILED
}

guard let cgImage = rep.cgImage else {
return CLIPBOARD_FAILED
}

pasteboard.clearContents()
pasteboard.writeObjects([NSImage(cgImage: cgImage, size: .zero)])

return CLIPBOARD_SUCCESS
}
}
2 changes: 1 addition & 1 deletion player/clipboard/clipboard-mac.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
static int init(struct clipboard_ctx *cl, struct clipboard_init_params *params)
{
struct clipboard_mac_priv *p = cl->priv = talloc_zero(cl, struct clipboard_mac_priv);
p->clipboard = [[Clipboard alloc] init];
p->clipboard = [[Clipboard alloc] initWithContext:cl];
return CLIPBOARD_SUCCESS;
}

Expand Down
10 changes: 10 additions & 0 deletions player/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -7169,6 +7169,16 @@ const struct mp_cmd_def mp_cmds[] = {
},
.spawn_thread = true,
},
{ "screenshot-to-clipboard", cmd_screenshot_to_clipboard,
{
{"flags", OPT_CHOICE(v.i,
{"video", 0},
{"window", 1},
{"subtitles", 2}),
OPTDEF_INT(2)},
},
.spawn_thread = true,
},
{ "screenshot-raw", cmd_screenshot_raw,
{
{"flags", OPT_CHOICE(v.i,
Expand Down
45 changes: 34 additions & 11 deletions player/screenshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "misc/dispatch.h"
#include "misc/node.h"
#include "misc/thread_tools.h"
#include "clipboard/clipboard.h"
#include "common/msg.h"
#include "options/path.h"
#include "video/mp_image.h"
Expand Down Expand Up @@ -405,10 +406,11 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode,
talloc_free(image);
return NULL;
}
struct mp_sws_context *sws = mp_sws_alloc(NULL);
mp_sws_scale(sws, nimage, image);
if (mp_image_swscale(nimage, image, mpctx->global, mpctx->log) < 0) {
talloc_free(nimage);
nimage = NULL;
}
talloc_free(image);
talloc_free(sws);
image = nimage;
}
}
Expand Down Expand Up @@ -449,14 +451,7 @@ struct mp_image *convert_image(struct mp_image *image, int destfmt,

dst->params = p;

struct mp_sws_context *sws = mp_sws_alloc(NULL);
sws->log = log;
if (global)
mp_sws_enable_cmdline_opts(sws, global);
bool ok = mp_sws_scale(sws, dst, image) >= 0;
talloc_free(sws);

if (!ok) {
if (mp_image_swscale(dst, image, global, log) < 0) {
mp_err(log, "Error when converting image.\n");
talloc_free(dst);
return NULL;
Expand Down Expand Up @@ -500,6 +495,34 @@ void cmd_screenshot_to_file(void *p)
talloc_free(image);
}

void cmd_screenshot_to_clipboard(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int mode = cmd->args[0].v.i;
struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts;
bool high_depth = image_writer_high_depth(&opts);
struct mp_image *image = screenshot_get(mpctx, mode, high_depth);
if (!image) {
mp_cmd_msg(cmd, MSGL_ERR, "Taking screenshot failed.");
cmd->success = false;
return;
}

struct clipboard_access_params params = {
.type = CLIPBOARD_DATA_IMAGE,
.target = CLIPBOARD_TARGET_CLIPBOARD,
};

struct clipboard_data item = {
.type = CLIPBOARD_DATA_IMAGE,
.u.image = image,
};

cmd->success = mp_clipboard_set_data(mpctx->clipboard, &params, &item) == CLIPBOARD_SUCCESS;
talloc_free(image);
}

void cmd_screenshot(void *p)
{
struct mp_cmd_ctx *cmd = p;
Expand Down
1 change: 1 addition & 0 deletions player/screenshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct mp_image *convert_image(struct mp_image *image, int destfmt,
// Handlers for the user-facing commands.
void cmd_screenshot(void *p);
void cmd_screenshot_to_file(void *p);
void cmd_screenshot_to_clipboard(void *p);
void cmd_screenshot_raw(void *p);

#endif /* MPLAYER_SCREENSHOT_H */
Loading
Loading