kopia lustrzana https://github.com/browsh-org/browsh
158 wiersze
4.8 KiB
JavaScript
158 wiersze
4.8 KiB
JavaScript
import BaseBuilder from 'dom/base_builder';
|
|
|
|
// Converts an instance of the viewport into a an array of pixel values.
|
|
// Note, that it does this both with and without the text visible in order
|
|
// to aid in a clean separation of the graphics and text in the final frame
|
|
// rendered in the terminal.
|
|
export default class GraphicsBuilder extends BaseBuilder {
|
|
constructor() {
|
|
super();
|
|
this.off_screen_canvas = document.createElement('canvas');
|
|
this.ctx = this.off_screen_canvas.getContext('2d');
|
|
this._updateCurrentViewportDimensions();
|
|
}
|
|
|
|
getPixelsAt(x, y) {
|
|
const pixel_data_start = parseInt(
|
|
(y * (this.viewport.width * 4)) + (x * 4)
|
|
);
|
|
let fg_colour = this.pixels_with_text.slice(
|
|
pixel_data_start, pixel_data_start + 3
|
|
);
|
|
let bg_colour = this.pixels_without_text.slice(
|
|
pixel_data_start, pixel_data_start + 3
|
|
);
|
|
return [fg_colour, bg_colour];
|
|
}
|
|
|
|
getScaledPixelAt(x, y) {
|
|
const pixel_data_start = (y * this.frame_width * 4) + (x * 4);
|
|
return this.scaled_pixels.slice(pixel_data_start, pixel_data_start + 3);
|
|
}
|
|
|
|
getSnapshotWithText() {
|
|
this._logPerformance(() => {
|
|
this._getSnapshotWithText();
|
|
}, 'get snapshot with text');
|
|
}
|
|
|
|
getSnapshotWithoutText() {
|
|
this._logPerformance(() => {
|
|
this._getSnapshotWithoutText();
|
|
}, 'get snapshot without text');
|
|
}
|
|
|
|
getScaledSnapshot(frame_width, frame_height) {
|
|
this._logPerformance(() => {
|
|
this._getScaledSnapshot(frame_width, frame_height);
|
|
}, 'get scaled snapshot');
|
|
}
|
|
|
|
_getSnapshotWithoutText() {
|
|
this._hideText();
|
|
this.pixels_without_text = this._getSnapshot();
|
|
this._showText();
|
|
return this.pixels_without_text;
|
|
}
|
|
|
|
_getSnapshotWithText() {
|
|
this.pixels_with_text = this._getSnapshot();
|
|
return this.pixels_with_text;
|
|
}
|
|
|
|
_getScaledSnapshot(frame_width, frame_height) {
|
|
this.frame_width = frame_width;
|
|
this.frame_height = frame_height;
|
|
this._scaleCanvas();
|
|
this.scaled_pixels = this._getSnapshot();
|
|
this._unScaleCanvas();
|
|
this._is_first_frame_finished = true;
|
|
return this.scaled_pixels;
|
|
}
|
|
|
|
_hideText() {
|
|
this.styles = document.createElement("style");
|
|
document.head.appendChild(this.styles);
|
|
this.styles.sheet.insertRule(
|
|
'html * {' +
|
|
' color: transparent !important;' +
|
|
// Note the disabling of transition effects here. Some websites have a fancy fade
|
|
// animation when changing colours, which we don't have time for in taking a snapshot.
|
|
// However, a drawback here is that, when we remove this style the transition actually
|
|
// kicks in - not that the terminal sees it because, by the nature of this style change
|
|
// here, we only ever capture the screen when text is invisible. However, I wonder if
|
|
// triggering color transitions for every frame might add some unnecessary load? What
|
|
// about permanently disabling color transitions in the global stylesheet?
|
|
' transition: color 0s !important;' +
|
|
'}'
|
|
);
|
|
}
|
|
|
|
_showText() {
|
|
this.styles.parentNode.removeChild(this.styles);
|
|
}
|
|
|
|
_getSnapshot() {
|
|
this._updateCurrentViewportDimensions()
|
|
let pixel_data = this._getPixelData();
|
|
return pixel_data;
|
|
}
|
|
|
|
// Deal with page scrolling and other viewport changes.
|
|
// Perhaps the window has been resized to better accommodate text-sizing, or to try
|
|
// to trigger some mobile responsive CSS.
|
|
_updateCurrentViewportDimensions() {
|
|
this.viewport = {
|
|
x_scroll: window.scrollX,
|
|
y_scroll: window.scrollY,
|
|
width: window.innerWidth,
|
|
height: window.innerHeight
|
|
}
|
|
if (!this.is_scaled) {
|
|
// Resize our canvas to match the viewport. I guess this makes for efficient
|
|
// use of memory?
|
|
this.off_screen_canvas.width = this.viewport.width;
|
|
this.off_screen_canvas.height = this.viewport.height;
|
|
}
|
|
}
|
|
|
|
// Scale the screenshot so that 1 pixel approximates half a TTY cell.
|
|
_scaleCanvas() {
|
|
this.is_scaled = true;
|
|
const scale_x = this.frame_width / this.viewport.width;
|
|
const scale_y = this.frame_height / this.viewport.height;
|
|
this._hideText();
|
|
this.ctx.save();
|
|
this.ctx.scale(scale_x, scale_y);
|
|
}
|
|
|
|
_unScaleCanvas() {
|
|
this.ctx.restore();
|
|
this._showText();
|
|
this.is_scaled = false;
|
|
}
|
|
|
|
// Get an array of RGB values.
|
|
// This is Firefox-only. Chrome has a nicer MediaStream for this.
|
|
_getPixelData() {
|
|
let width, height;
|
|
let background_colour = 'rgb(255,255,255)';
|
|
if (this.is_scaled) {
|
|
width = this.frame_width;
|
|
height = this.frame_height;
|
|
} else {
|
|
width = this.viewport.width;
|
|
height = this.viewport.height;
|
|
}
|
|
this.ctx.drawWindow(
|
|
window,
|
|
this.viewport.x_scroll,
|
|
this.viewport.y_scroll,
|
|
this.viewport.width,
|
|
this.viewport.height,
|
|
background_colour
|
|
);
|
|
return this.ctx.getImageData(0, 0, width, height).data;
|
|
}
|
|
}
|