Skip to content

Added Scaled outline and shadow support. #105

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

Merged
merged 1 commit into from
Feb 4, 2024
Merged
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
13 changes: 9 additions & 4 deletions README.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@ window.addEventListener("load",() => {
// load the fonts using opentype.js and put them in
// the fonts array.
// YOUR CODE HERE
// pass the font parsing function to the renderer
renderer = sabre.SABRERenderer(parseFont);
renderer.loadSubtitles(subs,fonts);
renderer.setViewport(1280,720); // use the video player's dimensions.
// initialize the renderer
renderer = sabre.SABRERenderer(parseFont,{fonts:fonts,subtitles:subs,colorSpace:sabre.VideoColorSpaces.AUTOMATIC,resolution:[1280,720],nativeResolution:[1280,720]});
// or you can do this:
// renderer = new sabre.SABRERenderer(parseFont);
// renderer.loadSubtitles(subs,fonts);
// renderer.setColorSpace(sabre.VideoColorSpaces.AUTOMATIC,1280,720);
// renderer.setViewport(1280,720);
// schedule your frame callback using either requestAnimationFrame or requestVideoFrameCallback
});
```
Expand All @@ -80,6 +83,8 @@ to render a frame of subtitles.

### API

The documentation generator is a little buggy, anytime it says something is global, that means it's a property of the `sabre.SABRERenderer` object.

{{>main}}

© 2012-2023 Patrick "ILOVEPIE" Rhodes Martin.
40 changes: 28 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@ window.addEventListener("load",() => {
// load the fonts using opentype.js and put them in
// the fonts array.
// YOUR CODE HERE
// pass the font parsing function to the renderer
renderer = sabre.SABRERenderer(parseFont);
renderer.loadSubtitles(subs,fonts);
renderer.setViewport(1280,720); // use the video player's dimensions.
// initialize the renderer
renderer = sabre.SABRERenderer(parseFont,{fonts:fonts,subtitles:subs,colorSpace:sabre.VideoColorSpaces.AUTOMATIC,resolution:[1280,720],nativeResolution:[1280,720]});
// or you can do this:
// renderer = new sabre.SABRERenderer(parseFont);
// renderer.loadSubtitles(subs,fonts);
// renderer.setColorSpace(sabre.VideoColorSpaces.AUTOMATIC,1280,720);
// renderer.setViewport(1280,720);
// schedule your frame callback using either requestAnimationFrame or requestVideoFrameCallback
});
```
Expand All @@ -80,17 +83,20 @@ to render a frame of subtitles.

### API

The documentation generator is a little buggy, anytime it says something is global, that means it's a property of the `sabre.SABRERenderer` object.

#### Functions

<dl>
<dt><a href="#loadSubtitles">loadSubtitles(subsText, fonts)</a> ⇒ <code>void</code></dt>
<dt><a href="#loadSubtitles">loadSubtitles(subtitles, fonts)</a> ⇒ <code>void</code></dt>
<dd><p>Begins the process of parsing the passed subtitles in SSA/ASS format into subtitle events.</p>
</dd>
<dt><a href="#setColorSpace">setColorSpace(colorSpace, [width], [height])</a></dt>
<dd><p>Configures the output colorspace to the set value (or guesses when automatic is specified based on resolution).
AUTOMATIC always assumes studio-swing (color values between 16-240), if you need full-swing (color values between 0-255)
Note: AUTOMATIC always assumes studio-swing (color values between 16-240), if you need full-swing (color values between 0-255)
that must be set by selecting AUTOMATIC_PC. AUTOMATIC and AUTOMATIC_PC are also incapable of determining if the
video is HDR, so you need to manually set either BT.2100_PQ or BT.2100_HLG if it is.</p>
video is HDR, so you need to manually set either BT.2100_PQ or BT.2100_HLG if it is.
Note: HDR support is stubbed and unimplemented currently.</p>
</dd>
<dt><a href="#setViewport">setViewport(width, height)</a> ⇒ <code>void</code></dt>
<dd><p>Updates the resolution (in CSS pixels) at which the subtitles are rendered (if the player is resized, for example).</p>
Expand All @@ -109,38 +115,44 @@ video is HDR, so you need to manually set either BT.2100_PQ or BT.2100_HLG if it
</dd>
</dl>

#### loadSubtitles(subsText, fonts) ⇒ <code>void</code>
<a name="loadSubtitles"></a>

#### loadSubtitles(subtitles, fonts) ⇒ <code>void</code>
Begins the process of parsing the passed subtitles in SSA/ASS format into subtitle events.

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
| subsText | <code>string</code> | the subtitle file's contents. |
| subtitles | <code>string</code> | the subtitle file's contents. |
| fonts | <code>Array.&lt;Font&gt;</code> | preloaded fonts necessary for this subtitle file (one of these MUST be Arial). |

<a name="setColorSpace"></a>

#### setColorSpace(colorSpace, [width], [height])
Configures the output colorspace to the set value (or guesses when automatic is specified based on resolution).
AUTOMATIC always assumes studio-swing (color values between 16-240), if you need full-swing (color values between 0-255)
Note: AUTOMATIC always assumes studio-swing (color values between 16-240), if you need full-swing (color values between 0-255)
that must be set by selecting AUTOMATIC_PC. AUTOMATIC and AUTOMATIC_PC are also incapable of determining if the
video is HDR, so you need to manually set either BT.2100_PQ or BT.2100_HLG if it is.
Note: HDR support is stubbed and unimplemented currently.

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
| colorSpace | <code>number</code> | the colorspace to use for output. |
| [width] | <code>number</code> | the x component of the video's resolution (only required when colorSpace is AUTOMATIC). |
| [height] | <code>number</code> | the y component of the video's resolution (only required when colorSpace is AUTOMATIC). |
| [width] | <code>number</code> | the x component of the video's resolution in regular pixels (only required when colorSpace is AUTOMATIC). |
| [height] | <code>number</code> | the y component of the video's resolution in regular pixels (only required when colorSpace is AUTOMATIC). |

<a name="setViewport"></a>

#### setViewport(width, height) ⇒ <code>void</code>
Updates the resolution (in CSS pixels) at which the subtitles are rendered (if the player is resized, for example).

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
Expand All @@ -154,12 +166,14 @@ Checks if the renderer is ready to render a frame.

**Kind**: global function
**Returns**: <code>boolean</code> - is the renderer ready?
**Access**: public
<a name="getFrame"></a>

#### getFrame(time) ⇒ <code>ImageBitmap</code>
Fetches a rendered frame of subtitles as an ImageBitmap, returns null if ImageBitmap is unsupported.

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
Expand All @@ -171,6 +185,7 @@ Fetches a rendered frame of subtitles as an ImageBitmap, returns null if ImageBi
Fetches a rendered frame of subtitles as an object uri.

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
Expand All @@ -183,6 +198,7 @@ Fetches a rendered frame of subtitles as an object uri.
Fetches a rendered frame of subtitles to a canvas.

**Kind**: global function
**Access**: public

| Param | Type | Description |
| --- | --- | --- |
Expand Down
1 change: 1 addition & 0 deletions include/canvas-2d-shape-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @typedef {!{
* renderEvent:function(number,SSASubtitleEvent,number,boolean):void,
* setPixelScaleRatio:function(number,number):void,
* setScaledOutlineAndShadowEnabled:function(boolean):void,
* getOffset:function():Array<number>,
* getOffsetExternal:function():Array<number>,
* getDimensions:function():Array<number>,
Expand Down
1 change: 1 addition & 0 deletions include/canvas-2d-text-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* renderGlyph:function(number,SSASubtitleEvent,{prevGlyph:?Glyph,glyph:?Glyph,breakOut:boolean},number,boolean):boolean,
* setRequestFont:function(!function(string,number,boolean):!{font:Font,foundItalic:boolean,foundWeight:number}):void,
* setPixelScaleRatio:function(number,number):void,
* setScaledOutlineAndShadowEnabled:function(boolean):void,
* getOffset:function():Array<number>,
* getOffsetExternal:function():Array<number>,
* getDimensions:function():Array<number>,
Expand Down
38 changes: 29 additions & 9 deletions src/canvas-2d-shape-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ const shape_renderer_prototype = global.Object.create(Object, {
writable: true
},

_scaledOutlineAndShadow: {
/**
* If the outline is scaled.
* @type {boolean}
*/
value: false,
writable: true
},

_canvas: {
/**
* The canvas for the shape renderer.
Expand Down Expand Up @@ -251,7 +260,7 @@ const shape_renderer_prototype = global.Object.create(Object, {
) {
//TODO: Figure out a good way to do dimension specific line widths.
let outline = this._calcOutline(time, style, overrides);
this._ctx.lineWidth = Math.min(outline.x, outline.y) * 2;
this._ctx.lineWidth = Math.min((!this._scaledOutlineAndShadow?outline.x/this._pixelScaleRatio.xratio:outline.x), (!this._scaledOutlineAndShadow?outline.y/this._pixelScaleRatio.yratio:outline.y)) * 2;
},
writable: false
},
Expand Down Expand Up @@ -636,12 +645,12 @@ const shape_renderer_prototype = global.Object.create(Object, {
pass === sabre.RenderPasses.BACKGROUND
) {
let outline = this._calcOutline(time, style, overrides);
this._width += outline.x * 2;
this._height += outline.x * 2;
this._offsetX += outline.x;
this._offsetY += outline.y;
outline_x = outline.x;
outline_y = outline.y;
outline_x = (!this._scaledOutlineAndShadow?outline.x/this._pixelScaleRatio.xratio:outline.x);
outline_y = (!this._scaledOutlineAndShadow?outline.y/this._pixelScaleRatio.yratio:outline.y);
this._width += outline_x * 2;
this._height += outline_y * 2;
this._offsetX += outline_x;
this._offsetY += outline_y;
}

let offsetXUnscaled = this._offsetX;
Expand All @@ -660,8 +669,8 @@ const shape_renderer_prototype = global.Object.create(Object, {
let shadowX = overrides.getShadowX() ?? shadowComponent;
let shadowY = overrides.getShadowY() ?? shadowComponent;
if (shadowX === 0 && shadowY === 0) noDraw = true;
this._offsetX -= shadowX;
this._offsetY -= shadowY;
this._offsetX -= shadowX/(this._scaledOutlineAndShadow?this._pixelScaleRatio.xratio:1);
this._offsetY -= shadowY/(this._scaledOutlineAndShadow?this._pixelScaleRatio.yratio:1);
} else if (borderStyle === sabre.BorderStyleModes.NONE) {
noDraw = true;
}
Expand Down Expand Up @@ -831,6 +840,17 @@ const shape_renderer_prototype = global.Object.create(Object, {
writable: false
},

"setScaledOutlineAndShadowEnabled": {
/**
* Sets whether the outline should be scaled when we are rendering to a higher resolution than the video.
* @param {boolean} enabled if true, the outline will be scaled.
*/
value: function setScaledOutlineEnabled (enabled) {
this._scaledOutlineAndShadow = enabled;
},
writable: false
},

"getOffset": {
/**
* Gets the offset of the resulting image.
Expand Down
39 changes: 30 additions & 9 deletions src/canvas-2d-text-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ const text_renderer_prototype = global.Object.create(Object, {
writable: true
},

_scaledOutlineAndShadow: {
/**
* If the outline is scaled.
* @type {boolean}
*/
value: false,
writable: true
},

_canvas: {
/**
* The canvas for the text renderer.
Expand Down Expand Up @@ -327,7 +336,7 @@ const text_renderer_prototype = global.Object.create(Object, {
value: function _setOutline (time, style, overrides) {
let outline = this._calcOutline(time, style, overrides);

this._ctx.lineWidth = Math.min(outline.x, outline.y) * 2;
this._ctx.lineWidth = Math.min((!this._scaledOutlineAndShadow?outline.x/this._pixelScaleRatio.xratio:outline.x), (!this._scaledOutlineAndShadow?outline.y/this._pixelScaleRatio.yratio:outline.y)) * 2;
},
writable: false
},
Expand Down Expand Up @@ -823,9 +832,9 @@ const text_renderer_prototype = global.Object.create(Object, {
}

this._eOffsetX +=
shadow.x * this._scale.x * this._pixelScaleRatio.xratio;
shadow.x * this._scale.x * (this._scaledOutlineAndShadow?this._pixelScaleRatio.xratio:1);
this._eOffsetY +=
shadow.y * this._scale.y * this._pixelScaleRatio.yratio;
shadow.y * this._scale.y * (this._scaledOutlineAndShadow?this._pixelScaleRatio.yratio:1);
} else if (borderStyle === sabre.BorderStyleModes.NONE) {
this._noDraw = true;
}
Expand Down Expand Up @@ -971,16 +980,17 @@ const text_renderer_prototype = global.Object.create(Object, {
outline.x *
2 *
this._scale.x *
this._pixelScaleRatio.xratio;
(this._scaledOutlineAndShadow?this._pixelScaleRatio.xratio:1);
this._height +=
outline.y *
2 *
this._scale.y *
this._pixelScaleRatio.yratio;
this._offsetX += outline.x;
this._offsetY += outline.y;
outline_x = outline.x;
outline_y = outline.y;
(this._scaledOutlineAndShadow?this._pixelScaleRatio.yratio:1);
outline_x = (!this._scaledOutlineAndShadow?outline.x/this._pixelScaleRatio.xratio:outline.x);
outline_y = (!this._scaledOutlineAndShadow?outline.y/this._pixelScaleRatio.yratio:outline.y);
this._offsetX += outline_x;
this._offsetY += outline_y;

}

let offsetXUnscaled = this._offsetX;
Expand Down Expand Up @@ -1187,6 +1197,17 @@ const text_renderer_prototype = global.Object.create(Object, {
writable: false
},

"setScaledOutlineAndShadowEnabled": {
/**
* Sets whether the outline should be scaled when we are rendering to a higher resolution than the video.
* @param {boolean} enabled if true, the outline will be scaled.
*/
value: function setScaledOutlineEnabled (enabled) {
this._scaledOutlineAndShadow = enabled;
},
writable: false
},

"getOffset": {
/**
* Gets the internal offset of the resulting image.
Expand Down
4 changes: 4 additions & 0 deletions src/font-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
/**
* An enum of platforms.
* @private
* @enum {number}
*/
const platforms = Object.freeze({
UNICODE: 0,
Expand All @@ -19,6 +21,8 @@ const platforms = Object.freeze({

/**
* An enum of name types.
* @private
* @enum {number}
*/
const nameTypes = Object.freeze({
COPYRIGHT: 0,
Expand Down
2 changes: 2 additions & 0 deletions src/global-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,14 @@ sabre["ColorSpaceConversionTypes"] = sabre.totalObjectFreeze({
});
/**
* Defines a non-constant luminance color space conversion.
* @private
* @typedef {!{type:number,offset:!Array<number>,toRGB:!Array<number>,toDisplayP3:!Array<number>,fromRGB:!Array<number>}}
*/
let NonConstantLuminanceColorSpaceConversion;

/**
* Defines a constant luminance color space conversion.
* @private
* @typedef {!{type:number,offset:!Array<number>,scale:!Array<number>,coefficients:!Array<number>,Nr:number,Nb:number,Pr:number,Pb:number}}
*/
let ConstantLuminanceColorSpaceConversion;
Expand Down
8 changes: 5 additions & 3 deletions src/renderer-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2991,8 +2991,8 @@ const renderer_prototype = global.Object.create(Object, {
* @return {Float32Array} result
*/
value: function _calcClipPathCoords (clip, inverse) {
let scale = /** @type {number} */ (clip[0]);
let path = /** @type {string} */ (clip[1]);
let scale = /** @private @type {number} */ (clip[0]);
let path = /** @private @type {string} */ (clip[1]);
let shapes = this._getShapesFromPath(scale, path);
let triangles = [];
for (let i = 0; i < shapes.length; i++) {
Expand Down Expand Up @@ -3344,7 +3344,7 @@ const renderer_prototype = global.Object.create(Object, {
].getTransitionAcceleration();
clip[0] = sabre.performTransition(
time,
/** @type {!number} */ (clip[0]),
/** @private @type {!number} */ (clip[0]),
transitionClip[0],
transitionStart,
transitionEnd,
Expand Down Expand Up @@ -4406,7 +4406,9 @@ const renderer_prototype = global.Object.create(Object, {
return _this._findFont(name, weight, italic);
};
this._textRenderer.setRequestFont(requestFont);
this._textRenderer.setScaledOutlineAndShadowEnabled(config.renderer["scaled_border_and_shadow"]);
this._textMaskRenderer.setRequestFont(requestFont);
this._textMaskRenderer.setScaledOutlineAndShadowEnabled(config.renderer["scaled_border_and_shadow"]);
this._config = config;
this._scheduler.setEvents(
/** @type {Array<SSASubtitleEvent>} */ (
Expand Down
Loading