<commit_before>/*
    Copyright (c) 2016 eyeOS

    This file is part of Open365.

    Open365 is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

wdi.RasterEngine = $.spcExtend(wdi.EventObject.prototype, {
	init: function(c) {
		this.time = Date.now();
		this.clientGui = c.clientGui;
	},

	dispose: function () {
		this.clientGui = null;
	},

	drawCanvas: function(spiceMessage) {
		wdi.Debug.log("rasterEngine",this.time);
		return this.clientGui.drawCanvas(spiceMessage);
	},

	removeCanvas: function(spiceMessage) {
		return this.clientGui.removeCanvas(spiceMessage);
	},

	invalList: function(spiceMessage) {
		var items = spiceMessage.args.items;
		var item = null;
		for(var i in items) {
			item = items[i];
			wdi.ImageCache.delImage(item.id);
		}
	},

	handleStreamCreate: function(spiceMessage) {
		var stream = spiceMessage.args;
		stream.computedBox = wdi.graphics.getBoxFromSrcArea(stream.rect);
		wdi.Stream.addStream(spiceMessage.args.id, stream);
	},

	handleStreamData: function(spiceMessage) {
		var imageData = spiceMessage.args.data; //jpeg string encoded
		var stream = wdi.Stream.getStream(spiceMessage.args.id); //recover the stream
		var context = this.clientGui.getContext(stream.surface_id);
		var img = wdi.GlobalPool.create('Image'); //auto-release pool
		wdi.ExecutionControl.sync = true;
		var url;
		img.onload = function() {
			URL.revokeObjectURL(url);
			var box = stream.computedBox;
			// we only rotate the stream if spice tells us so through the TOP_DOWN flag mask
			if (!stream.flags & wdi.SpiceStreamFlags.SPICE_STREAM_FLAGS_TOP_DOWN) {
				var offsetX = box.x + (this.width/2);
				var offsetY = box.y + (this.height/2);
				context.save();
				context.translate(offsetX, offsetY);
				context.rotate(Math.PI);
				context.scale(-1,1);
				context.drawImage(this, box.x-offsetX, box.y-offsetY, box.width, box.height);
				context.restore();
			} else {
				context.drawImage(this, box.x, box.y, box.width, box.height);
			}
		};

		img.onerror = function() {
			URL.revokeObjectURL(url)
		};

		url = wdi.SpiceObject.bytesToURI(imageData);
		img.src = url;
	},

	handleStreamClip: function(spiceMessage) {
		wdi.Stream.clip(spiceMessage.args.id, spiceMessage.args.clip)
	},

	handleStreamDestroy: function(spiceMessage) {
		wdi.Stream.deleteStream(spiceMessage.args.id);
	},

	drawRop3: function(spiceMessage) {
		var box = wdi.graphics.getBoxFromSrcArea(spiceMessage.args.base.box);
		var context = this.clientGui.getContext(spiceMessage.args.base.surface_id);
		var destImg = context.getImageData(box.x, box.y, box.width, box.height);
		var clientGui = this.clientGui;

		var brush = spiceMessage.args.brush;
		var rop = spiceMessage.args.rop_descriptor;

		var srcArea = wdi.graphics.getBoxFromSrcArea(spiceMessage.args.src_area);
		wdi.graphics.getImageFromSpice(spiceMessage.args.src_image.imageDescriptor, spiceMessage.args.src_image.data, this.clientGui, function (sourceCanvas) {
			if (sourceCanvas) {
				//Get source image data (image coming from the packet)
				var sourceContext = sourceCanvas.getContext('2d');
				var srcImg = sourceContext.getImageData(srcArea.x, srcArea.y, srcArea.width, srcArea.height);
				var srcImgData = srcImg.data; //this

				//Get pattern image data
				//brush
				var tmpcanvas = wdi.graphics.getNewTmpCanvas(box.width, box.height);
				var tmpcontext = tmpcanvas.getContext('2d');
				var brushBox = {
					width: box.width,
					height: box.height,
					x: 0,
					y: 0
				};
				wdi.graphics.setBrush(clientGui, tmpcontext, brush, brushBox, wdi.SpiceRopd.SPICE_ROPD_OP_PUT);//Without alpha?
				var pattern = tmpcontext.getImageData(0, 0, box.width, box.height);
				var patImgData = pattern.data; //this

				//Get dest image data
				var destImgData = destImg.data;

				//Get result image data
				tmpcanvas = wdi.graphics.getNewTmpCanvas(box.width, box.height);
				tmpcontext = tmpcanvas.getContext('2d');
				var result = tmpcontext.createImageData(box.width, box.height);
				var resultData = result.data;

				if ((srcImg.width != pattern.width || srcImg.width != destImg.width) || (srcImg.height != pattern.height || srcImg.height != destImg.height)) {
					//TODO: resize
				}

				//Do the Ternary Raster Operation
				var length = destImgData.length;//Could be anyone
				var func = wdi.Rop3[rop];
				for (var i = 0;i<length;i+=4) {
					resultData[i] = func(patImgData[i], srcImgData[i], destImgData[i]) & 255;
					resultData[i+1] = func(patImgData[i+1], srcImgData[i+1], destImgData[i+1]) & 255;
					resultData[i+2] = func(patImgData[i+2], srcImgData[i+2], destImgData[i+2]) & 255;
					resultData[i+3] = 255;
				}

				tmpcontext.putImageData(result, 0, 0);

				this.drawClip(tmpcanvas, box, context);
			} else {
				wdi.Debug.log('Unable to get image!');
			}
		}, this);
	},

	drawInvers: function(spiceMessage) {
		var drawBase = spiceMessage.args.base;
		var box = wdi.graphics.getBoxFromSrcArea(drawBase.box);

		var surface_id = drawBase.surface_id;

		var context = this.clientGui.getContext(surface_id);

		var destImg = wdi.graphics.getRect(box, context.canvas);
		var imageData = wdi.RasterOperation.process(wdi.SpiceRopd.SPICE_ROPD_OP_INVERS, null, destImg);//this operation modifies destination

		context.drawImage(imageData, box.x, box.y, box.width, box.height);
	},

	drawStroke: function(spiceMessage) {
		var stroke = spiceMessage.args,
			context = this.clientGui.getContext(spiceMessage.args.base.surface_id),
			color = stroke.brush.color.html_color,
			lineWidth = 1,
			pointsLength,
			firstPoint,
			i,
			j,
			length = stroke.path.num_segments,
			seg;

		if (stroke.attr.flags & wdi.SpiceLineFlags.SPICE_LINE_FLAGS_STYLED) {
			wdi.Debug.log('SPICE_LINE_FLAGS_STYLED');
		}

		for (var i = 0;i < length; i++) {
			seg = stroke.path.segments[i];

			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_BEGIN) {
				context.beginPath();
				context.moveTo(seg.points[0].x, seg.points[0].y);
				context.strokeStyle = color;
				context.lineWidth = lineWidth;
			}
			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_BEZIER) {
				pointsLength = seg.points.length;
				if (pointsLength % 3 == 0) {
					for (j = 0; j < pointsLength; j += 3) {
						context.bezierCurveTo(
							seg.points[j].x, seg.points[j].y,
							seg.points[j+1].x, seg.points[j+1].y,
							seg.points[j+2].x, seg.points[j+2].y
						);
					}
				}
			} else {
				pointsLength = seg.points.length;

				for (j = 0; j < pointsLength; j++) {
					if (j == 0) firstPoint = seg.points[j];
					context.lineTo(seg.points[j].x + (lineWidth / 2), seg.points[j].y + (lineWidth / 2));
				}
			}
			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_END) {
				if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_CLOSE) {
					context.lineTo(firstPoint.x + (lineWidth / 2), firstPoint.y + (lineWidth / 2));
				}
				context.stroke();
				context.closePath();
			}
		}
	},

	drawImage: function(spiceMessage) {
		var args = spiceMessage.args;
		var drawBase = args.base;
		var surface_id = drawBase.surface_id;
		var rop = args.rop_descriptor;

		var scale = args.scale_mode;


		//calculate src_area box
		var box_origin = wdi.graphics.getBoxFromSrcArea(args.src_area);

		var box_dest = wdi.graphics.getBoxFromSrcArea(drawBase.box);

		if (window.vdiLoadTest && window.firstImage === undefined) {
			window.firstImage = true;
		}

		//get the image in imagedata format
		wdi.graphics.getImageFromSpice(args.image.imageDescriptor, args.image.data, this.clientGui, function(srcImg) {
			//we have image?
			if(srcImg) {
				if (window.firstImage) {
					var data;
					if(srcImg.getContext) {
						data = srcImg.getContext('2d').getImageData(0, 0, srcImg.width, srcImg.height).data.buffer.slice(0)
					} else {
						data = srcImg.data.buffer.slice(0);
					}
					window.firstImageData = data;
					window.firstImage = false;
				}

				//adapt to src_area
				srcImg = wdi.graphics.getRect(box_origin, srcImg);
				if(box_origin.width !== box_dest.width && box_origin.height !== box_dest.height) {
					srcImg = wdi.graphics.getImageFromData(srcImg);
					var newSrcImg = wdi.graphics.getNewTmpCanvas(box_dest.width, box_dest.height);
					var tmpcontext = newSrcImg.getContext('2d');
					tmpcontext.drawImage(srcImg, 0, 0, box_origin.width, box_origin.height, 0, 0, box_dest.width, box_dest.height);
					srcImg = newSrcImg;
				}

				//depending on the rop, we can avoid to get destImg
				if (rop !== wdi.SpiceRopd.SPICE_ROPD_OP_PUT) {
					//rop
					var destImg = wdi.graphics.getRect(box_dest, this.clientGui.getCanvas(surface_id));
					srcImg = wdi.RasterOperation.process(rop, srcImg, destImg);
				}

				var context = this.clientGui.getContext(surface_id);

				if(srcImg instanceof ImageData) {
					context.putImageData(srcImg, box_dest.x, box_dest.y, 0, 0, box_dest.width, box_dest.height);
				} else {
					context.drawImage(srcImg, box_dest.x, box_dest.y, box_dest.width, box_dest.height);
				}

			} else {
				//failed to get image, cache error?
				wdi.Debug.log('Unable to get image!');
			}
		}, this, {'opaque':true, 'brush': args.brush, 'raw': false});
	},

	drawClip: function(srcImg, box, context) {
		context.drawImage(srcImg, box.x, box.y, box.width, box.height);
	},

	drawFill: function(spiceMessage) {
		var args = spiceMessage.args;
		var context = this.clientGui.getContext(args.base.surface_id);
		var box = wdi.graphics.getBoxFromSrcArea(args.base.box);
		var brush = args.brush;
		var ropd = args.rop_descriptor;

		wdi.graphics.setBrush(this.clientGui, context, brush, box, ropd);
	},

	drawCopyBits: function(spiceMessage) {
		var drawBase = spiceMessage.args.base;
		var surface_id = drawBase.surface_id;
		var src_position = spiceMessage.args.src_position;<commit_msg>Improved drawImage performance by avoding to create subrectangles<commit_after>/*
    Copyright (c) 2016 eyeOS

    This file is part of Open365.

    Open365 is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

wdi.RasterEngine = $.spcExtend(wdi.EventObject.prototype, {
	init: function(c) {
		this.time = Date.now();
		this.clientGui = c.clientGui;
	},

	dispose: function () {
		this.clientGui = null;
	},

	drawCanvas: function(spiceMessage) {
		wdi.Debug.log("rasterEngine",this.time);
		return this.clientGui.drawCanvas(spiceMessage);
	},

	removeCanvas: function(spiceMessage) {
		return this.clientGui.removeCanvas(spiceMessage);
	},

	invalList: function(spiceMessage) {
		var items = spiceMessage.args.items;
		var item = null;
		for(var i in items) {
			item = items[i];
			wdi.ImageCache.delImage(item.id);
		}
	},

	handleStreamCreate: function(spiceMessage) {
		var stream = spiceMessage.args;
		stream.computedBox = wdi.graphics.getBoxFromSrcArea(stream.rect);
		wdi.Stream.addStream(spiceMessage.args.id, stream);
	},

	handleStreamData: function(spiceMessage) {
		var imageData = spiceMessage.args.data; //jpeg string encoded
		var stream = wdi.Stream.getStream(spiceMessage.args.id); //recover the stream
		var context = this.clientGui.getContext(stream.surface_id);
		var img = wdi.GlobalPool.create('Image'); //auto-release pool
		wdi.ExecutionControl.sync = true;
		var url;
		img.onload = function() {
			URL.revokeObjectURL(url);
			var box = stream.computedBox;
			// we only rotate the stream if spice tells us so through the TOP_DOWN flag mask
			if (!stream.flags & wdi.SpiceStreamFlags.SPICE_STREAM_FLAGS_TOP_DOWN) {
				var offsetX = box.x + (this.width/2);
				var offsetY = box.y + (this.height/2);
				context.save();
				context.translate(offsetX, offsetY);
				context.rotate(Math.PI);
				context.scale(-1,1);
				context.drawImage(this, box.x-offsetX, box.y-offsetY, box.width, box.height);
				context.restore();
			} else {
				context.drawImage(this, box.x, box.y, box.width, box.height);
			}
		};

		img.onerror = function() {
			URL.revokeObjectURL(url)
		};

		url = wdi.SpiceObject.bytesToURI(imageData);
		img.src = url;
	},

	handleStreamClip: function(spiceMessage) {
		wdi.Stream.clip(spiceMessage.args.id, spiceMessage.args.clip)
	},

	handleStreamDestroy: function(spiceMessage) {
		wdi.Stream.deleteStream(spiceMessage.args.id);
	},

	drawRop3: function(spiceMessage) {
		var box = wdi.graphics.getBoxFromSrcArea(spiceMessage.args.base.box);
		var context = this.clientGui.getContext(spiceMessage.args.base.surface_id);
		var destImg = context.getImageData(box.x, box.y, box.width, box.height);
		var clientGui = this.clientGui;

		var brush = spiceMessage.args.brush;
		var rop = spiceMessage.args.rop_descriptor;

		var srcArea = wdi.graphics.getBoxFromSrcArea(spiceMessage.args.src_area);
		wdi.graphics.getImageFromSpice(spiceMessage.args.src_image.imageDescriptor, spiceMessage.args.src_image.data, this.clientGui, function (sourceCanvas) {
			if (sourceCanvas) {
				//Get source image data (image coming from the packet)
				var sourceContext = sourceCanvas.getContext('2d');
				var srcImg = sourceContext.getImageData(srcArea.x, srcArea.y, srcArea.width, srcArea.height);
				var srcImgData = srcImg.data; //this

				//Get pattern image data
				//brush
				var tmpcanvas = wdi.graphics.getNewTmpCanvas(box.width, box.height);
				var tmpcontext = tmpcanvas.getContext('2d');
				var brushBox = {
					width: box.width,
					height: box.height,
					x: 0,
					y: 0
				};
				wdi.graphics.setBrush(clientGui, tmpcontext, brush, brushBox, wdi.SpiceRopd.SPICE_ROPD_OP_PUT);//Without alpha?
				var pattern = tmpcontext.getImageData(0, 0, box.width, box.height);
				var patImgData = pattern.data; //this

				//Get dest image data
				var destImgData = destImg.data;

				//Get result image data
				tmpcanvas = wdi.graphics.getNewTmpCanvas(box.width, box.height);
				tmpcontext = tmpcanvas.getContext('2d');
				var result = tmpcontext.createImageData(box.width, box.height);
				var resultData = result.data;

				if ((srcImg.width != pattern.width || srcImg.width != destImg.width) || (srcImg.height != pattern.height || srcImg.height != destImg.height)) {
					//TODO: resize
				}

				//Do the Ternary Raster Operation
				var length = destImgData.length;//Could be anyone
				var func = wdi.Rop3[rop];
				for (var i = 0;i<length;i+=4) {
					resultData[i] = func(patImgData[i], srcImgData[i], destImgData[i]) & 255;
					resultData[i+1] = func(patImgData[i+1], srcImgData[i+1], destImgData[i+1]) & 255;
					resultData[i+2] = func(patImgData[i+2], srcImgData[i+2], destImgData[i+2]) & 255;
					resultData[i+3] = 255;
				}

				tmpcontext.putImageData(result, 0, 0);

				this.drawClip(tmpcanvas, box, context);
			} else {
				wdi.Debug.log('Unable to get image!');
			}
		}, this);
	},

	drawInvers: function(spiceMessage) {
		var drawBase = spiceMessage.args.base;
		var box = wdi.graphics.getBoxFromSrcArea(drawBase.box);

		var surface_id = drawBase.surface_id;

		var context = this.clientGui.getContext(surface_id);

		var destImg = wdi.graphics.getRect(box, context.canvas);
		var imageData = wdi.RasterOperation.process(wdi.SpiceRopd.SPICE_ROPD_OP_INVERS, null, destImg);//this operation modifies destination

		context.drawImage(imageData, box.x, box.y, box.width, box.height);
	},

	drawStroke: function(spiceMessage) {
		var stroke = spiceMessage.args,
			context = this.clientGui.getContext(spiceMessage.args.base.surface_id),
			color = stroke.brush.color.html_color,
			lineWidth = 1,
			pointsLength,
			firstPoint,
			i,
			j,
			length = stroke.path.num_segments,
			seg;

		if (stroke.attr.flags & wdi.SpiceLineFlags.SPICE_LINE_FLAGS_STYLED) {
			wdi.Debug.log('SPICE_LINE_FLAGS_STYLED');
		}

		for (var i = 0;i < length; i++) {
			seg = stroke.path.segments[i];

			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_BEGIN) {
				context.beginPath();
				context.moveTo(seg.points[0].x, seg.points[0].y);
				context.strokeStyle = color;
				context.lineWidth = lineWidth;
			}
			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_BEZIER) {
				pointsLength = seg.points.length;
				if (pointsLength % 3 == 0) {
					for (j = 0; j < pointsLength; j += 3) {
						context.bezierCurveTo(
							seg.points[j].x, seg.points[j].y,
							seg.points[j+1].x, seg.points[j+1].y,
							seg.points[j+2].x, seg.points[j+2].y
						);
					}
				}
			} else {
				pointsLength = seg.points.length;

				for (j = 0; j < pointsLength; j++) {
					if (j == 0) firstPoint = seg.points[j];
					context.lineTo(seg.points[j].x + (lineWidth / 2), seg.points[j].y + (lineWidth / 2));
				}
			}
			if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_END) {
				if (seg.flags & wdi.SpicePathFlags.SPICE_PATH_CLOSE) {
					context.lineTo(firstPoint.x + (lineWidth / 2), firstPoint.y + (lineWidth / 2));
				}
				context.stroke();
				context.closePath();
			}
		}
	},

	drawImage: function(spiceMessage) {
		var args = spiceMessage.args;
		var drawBase = args.base;
		var surface_id = drawBase.surface_id;
		var rop = args.rop_descriptor;

		var scale = args.scale_mode;


		//calculate src_area box
		var box_origin = wdi.graphics.getBoxFromSrcArea(args.src_area);

		var box_dest = wdi.graphics.getBoxFromSrcArea(drawBase.box);

		if (window.vdiLoadTest && window.firstImage === undefined) {
			window.firstImage = true;
		}

		//get the image in imagedata format
		wdi.graphics.getImageFromSpice(args.image.imageDescriptor, args.image.data, this.clientGui, function(srcImg) {
			//we have image?
			if(srcImg) {
				if (window.firstImage) {
					var data;
					if(srcImg.getContext) {
						data = srcImg.getContext('2d').getImageData(0, 0, srcImg.width, srcImg.height).data.buffer.slice(0)
					} else {
						data = srcImg.data.buffer.slice(0);
					}
					window.firstImageData = data;
					window.firstImage = false;
				}

				var rectangle = false;

				if(box_origin.width !== box_dest.width && box_origin.height !== box_dest.height) {
					rectangle = true;
					srcImg = wdi.graphics.getRect(box_origin, srcImg);
					srcImg = wdi.graphics.getImageFromData(srcImg);
					var newSrcImg = wdi.graphics.getNewTmpCanvas(box_dest.width, box_dest.height);
					var tmpcontext = newSrcImg.getContext('2d');
					tmpcontext.drawImage(srcImg, 0, 0, box_origin.width, box_origin.height, 0, 0, box_dest.width, box_dest.height);
					srcImg = newSrcImg;
				}

				//depending on the rop, we can avoid to get destImg
				if (rop !== wdi.SpiceRopd.SPICE_ROPD_OP_PUT) {
					//rop
					if(!rectangle) {
						srcImg = wdi.graphics.getRect(box_origin, srcImg);
					}
					var destImg = wdi.graphics.getRect(box_dest, this.clientGui.getCanvas(surface_id));
					srcImg = wdi.RasterOperation.process(rop, srcImg, destImg);
				}

				var context = this.clientGui.getContext(surface_id);

				if(srcImg instanceof ImageData) {
					if(rectangle) { //a subrectangle has been created, use it
						context.putImageData(srcImg, box_dest.x, box_dest.y, 0, 0, box_dest.width, box_dest.height);
					} else {
						context.putImageData(srcImg, box_dest.x, box_dest.y, box_origin.x, box_origin.y, box_origin.width, box_origin.height);
					}
				} else {
					if(rectangle) {
						context.drawImage(srcImg, box_dest.x, box_dest.y, box_dest.width, box_dest.height);
					} else {
						context.drawImage(srcImg, box_origin.x, box_origin.y, box_origin.width, box_origin.height, box_dest.x, box_dest.y, box_dest.width, box_dest.height);
					}
				}

			} else {
				//failed to get image, cache error?
				wdi.Debug.log('Unable to get image!');
			}
		}, this, {'opaque':true, 'brush': args.brush, 'raw': false});
	},

	drawClip: function(srcImg, box, context) {
		context.drawImage(srcImg, box.x, box.y, box.width, box.height);
	},

	drawFill: function(spiceMessage) {
		var args = spiceMessage.args;
		var context = this.clientGui.getContext(args.base.surface_id);
		var box = wdi.graphics.getBoxFromSrcArea(args.base.box);
		var brush = args.brush;
		var ropd = args.rop_descriptor;

		wdi.graphics.setBrush(this.clientGui, context, brush, box, ropd);
	},

	drawCopyBits: function(spiceMessage) {
		var drawBase = spiceMessage.args.base;
		var surface_id = drawBase.surface_id;
		var src_position = spiceMessage.args.src_position;
{'fraction_non_alphanumeric': 0.1297187617969045, 'fraction_numerical': 0.007455643639109098, 'mean_word_length': 3.5882225589954535, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3055898', 'n_tokens_mistral': 8591, 'n_tokens_neox': 7308, 'n_words': 1519}