Skip to content

Commit

Permalink
Render to images instead of canvas elements
Browse files Browse the repository at this point in the history
  • Loading branch information
ajayyy committed Jun 22, 2023
1 parent b498b8c commit 7261f91
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 65 deletions.
2 changes: 1 addition & 1 deletion public/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
--cb-thumbnail-padding: 0.3em;
}

canvas.ytd-img-shadow:not(.ytp-autonav-endscreen-upnext-thumbnail, .ytp-videowall-still-image) {
.cbCustomThumbnailCanvas.ytd-img-shadow:not(.ytp-autonav-endscreen-upnext-thumbnail, .ytp-videowall-still-image) {
display: block;
margin-left: var(--yt-img-margin-left, auto);
margin-right: var(--yt-img-margin-right, auto);
Expand Down
14 changes: 0 additions & 14 deletions public/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,20 +177,6 @@
</div>
</div>

<div data-type="toggle" data-sync="antiAliasThumbnails">
<div class="switch-container">
<label class="switch">
<input id="antiAliasThumbnails" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="antiAliasThumbnails">
__MSG_antiAliasThumbnails__
</label>
</div>

<div class="small-description">__MSG_whatAntiAliasThumbnails__</div>
</div>

<div data-type="toggle" data-sync="showGuidelineHelp">
<div class="switch-container">
<label class="switch">
Expand Down
2 changes: 0 additions & 2 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ interface SBConfig {
startLocalRenderTimeout: number;
renderTimeout: number;
thumbnailCacheUse: ThumbnailCacheOption;
antiAliasThumbnails: boolean;
showGuidelineHelp: boolean;
thumbnailFallback: ThumbnailFallbackOption;
extensionEnabled: boolean;
Expand Down Expand Up @@ -120,7 +119,6 @@ const syncDefaults = {
startLocalRenderTimeout: 2000,
renderTimeout: 25000,
thumbnailCacheUse: ThumbnailCacheOption.OnAllPages,
antiAliasThumbnails: true,
showGuidelineHelp: true,
thumbnailFallback: ThumbnailFallbackOption.RandomTime,
extensionEnabled: true,
Expand Down
6 changes: 3 additions & 3 deletions src/submission/ThumbnailComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { drawCentered, renderThumbnail } from "../thumbnails/thumbnailRenderer";
import { drawCenteredToCanvas, renderThumbnail } from "../thumbnails/thumbnailRenderer";
import { waitFor } from "@ajayyy/maze-utils"
import { VideoID } from "@ajayyy/maze-utils/lib/video";
import { ThumbnailSubmission } from "../thumbnails/thumbnailData";
Expand Down Expand Up @@ -86,7 +86,7 @@ export const ThumbnailComponent = (props: ThumbnailComponentProps) => {
if (rendered) {
const imageBitmap = await createImageBitmap(rendered.blob);

drawCentered(canvasRef.current!, canvasRef.current!.width, canvasRef.current!.height,
drawCenteredToCanvas(canvasRef.current!, canvasRef.current!.width, canvasRef.current!.height,
imageBitmap.width, imageBitmap.height, imageBitmap);
} else {
props.onError(chrome.i18n.getMessage("FailedToRender"));
Expand Down Expand Up @@ -159,7 +159,7 @@ async function renderCurrentFrame(props: ThumbnailComponentProps,

props.onError("");
canvasRef.current!.getContext("2d")!.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height);
drawCentered(canvasRef.current!, canvasRef.current!.width, canvasRef.current!.height, props.video.videoWidth, props.video.videoHeight, props.video);
drawCenteredToCanvas(canvasRef.current!, canvasRef.current!.width, canvasRef.current!.height, props.video.videoWidth, props.video.videoHeight, props.video);

if (waitForNextFrame && !props.video.paused && !inRenderingLoop.current) {
inRenderingLoop.current = true;
Expand Down
67 changes: 24 additions & 43 deletions src/thumbnails/thumbnailRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,17 @@ function handleThumbnailRenderFailure(videoID: VideoID, width: number, height: n
}

function renderToBlob(surface: HTMLVideoElement | HTMLCanvasElement): Promise<Blob> {
const width = surface instanceof HTMLVideoElement ? surface.videoWidth : surface.width;
const height = surface instanceof HTMLVideoElement ? surface.videoHeight : surface.height;
if (surface instanceof HTMLVideoElement) {
const canvas = document.createElement("canvas");
canvas.width = surface.videoWidth;
canvas.height = surface.videoHeight;
canvas.getContext("2d")!.drawImage(surface, 0, 0);

const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.getContext("2d")!.drawImage(surface, 0, 0);
surface = canvas;
}

return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
(surface as HTMLCanvasElement).toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
Expand All @@ -266,10 +267,10 @@ function renderToBlob(surface: HTMLVideoElement | HTMLCanvasElement): Promise<Bl
*
* Starts with lower resolution and replaces it with higher resolution when ready.
*/
export async function createThumbnailCanvas(existingCanvas: HTMLCanvasElement | null, videoID: VideoID, width: number,
export async function createThumbnailImageElement(existingElement: HTMLImageElement | null, videoID: VideoID, width: number,
height: number, brandingLocation: BrandingLocation, forcedTimestamp: number | null,
saveVideo: boolean, stillValid: () => Promise<boolean>, ready: (canvas: HTMLCanvasElement) => unknown,
failure: () => unknown): Promise<HTMLCanvasElement | null> {
saveVideo: boolean, stillValid: () => Promise<boolean>, ready: (image: HTMLImageElement) => unknown,
failure: () => unknown): Promise<HTMLImageElement | null> {

let timestamp = forcedTimestamp as number;
if (timestamp === null) {
Expand Down Expand Up @@ -302,10 +303,8 @@ export async function createThumbnailCanvas(existingCanvas: HTMLCanvasElement |
return null;
}

const canvas = existingCanvas ?? document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.style.display = "none";
const image = existingElement ?? document.createElement("img");
image.style.display = "none";

const result = async (canvasInfo: RenderedThumbnailVideo | null) => {
if (!await stillValid()) {
Expand All @@ -317,9 +316,8 @@ export async function createThumbnailCanvas(existingCanvas: HTMLCanvasElement |
return;
}

const imageBitmap = await createImageBitmap(canvasInfo.blob);
drawCentered(canvas, width, height, imageBitmap.width, imageBitmap.height, imageBitmap);
ready(canvas);
drawBlob(image, canvasInfo.blob);
ready(image);
}

renderThumbnail(videoID, width, height, saveVideo, timestamp).then(result).catch(() => {
Expand All @@ -329,43 +327,26 @@ export async function createThumbnailCanvas(existingCanvas: HTMLCanvasElement |
});
});

return canvas;
return image;
}

export function drawBlob(image: HTMLImageElement, blob: Blob): void {
image.src = URL.createObjectURL(blob);
}

export function drawCentered(canvas: HTMLCanvasElement, width: number, height: number,
export function drawCenteredToCanvas(canvas: HTMLCanvasElement, width: number, height: number,
originalWidth: number, originalHeight: number, originalSurface: HTMLVideoElement | HTMLCanvasElement | ImageBitmap): void {
const calculateWidth = height * originalWidth / originalHeight;
const context = canvas.getContext("2d")!;

if (Config.config!.antiAliasThumbnails
&& (originalSurface.width !== width || originalSurface.height !== height)) {
originalSurface = runAntiAliasShrink(originalSurface, width, height);
context.imageSmoothingEnabled = true;
}

context.drawImage(originalSurface, (width - calculateWidth) / 2, 0, calculateWidth, height);
}

function runAntiAliasShrink(originalSurface: HTMLVideoElement | HTMLCanvasElement | ImageBitmap,
width: number, height: number): HTMLVideoElement | HTMLCanvasElement | ImageBitmap {
while (originalSurface.width > width * 2 && originalSurface.height > height * 2) {
const newCanvas = document.createElement("canvas");
newCanvas.width = originalSurface.width / 1.5;
newCanvas.height = originalSurface.height / 1.5;
const newContext = newCanvas.getContext("2d")!;
newContext.imageSmoothingEnabled = true;
newContext.drawImage(originalSurface, 0, 0, newCanvas.width, newCanvas.height);

originalSurface = newCanvas;
}

return originalSurface;
}

function createVideo(existingVideo: HTMLVideoElement | null, url: string, timestamp: number): HTMLVideoElement {
if (timestamp === 0 && !isFirefoxOrSafari()) timestamp += 0.001;

const video = existingVideo ?? document.createElement("video");
video.crossOrigin = "anonymous";
// https://stackoverflow.com/a/69074004
if (!existingVideo) video.src = `${url}#t=${timestamp}-${timestamp + 0.001}`;
video.currentTime = timestamp;
Expand Down Expand Up @@ -457,10 +438,10 @@ export async function replaceThumbnail(element: HTMLElement, videoID: VideoID, b
}
}).catch(logError);

const existingCanvas = image.parentElement?.querySelector(".cbCustomThumbnailCanvas") as HTMLCanvasElement | null;
const existingImageElement = image.parentElement?.querySelector(".cbCustomThumbnailCanvas") as HTMLImageElement | null;

try {
const thumbnail = await createThumbnailCanvas(existingCanvas, videoID, width, height, brandingLocation, timestamp ?? null, false, async () => {
const thumbnail = await createThumbnailImageElement(existingImageElement, videoID, width, height, brandingLocation, timestamp ?? null, false, async () => {
return brandingLocation === BrandingLocation.Watch ? getVideoID() === videoID
: await extractVideoIDFromElement(element, brandingLocation) === videoID;
}, (thumbnail) => {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/cssInjector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function buildHideThumbnailCss(): string {
];

for (const thumbnailType of thumbnailTypes) {
result.push(`${start} ${thumbnailType} img:not(.cb-visible, ytd-moving-thumbnail-renderer img)`);
result.push(`${start} ${thumbnailType} img:not(.cb-visible, ytd-moving-thumbnail-renderer img, .cbCustomThumbnailCanvas)`);
}
}

Expand Down

0 comments on commit 7261f91

Please sign in to comment.