Преглед на файлове

Crop与Resize综合处理

世祺 преди 1 година
родител
ревизия
95c6310e00
променени са 1 файла, в които са добавени 239 реда и са изтрити 115 реда
  1. 239 115
      src/worker.js

+ 239 - 115
src/worker.js

@@ -12,33 +12,230 @@ const aws = new AwsClient({
 	// retries,         // number of retries before giving up, defaults to 10, set to 0 for no retrying
 	// initRetryMs,     // defaults to 50 – timeout doubles each retry
 });
-const aws_base_url = 'plat-sg-cloudflare-testing.s3.ap-southeast-1.amazonaws.com';
-const self_host = 'cf-test.hoyoverse.com';
-const text_to_image_url = 'https://text-to-image.hoyoverse.workers.dev/';
-const watermark_fg = 'fff';
-const watermark_bg = '000';
+const globalConfiguration = {
+	aws_base_url: 'plat-sg-cloudflare-testing.s3.ap-southeast-1.amazonaws.com',
+	self_host: 'cf-test.hoyoverse.com',
+	text_to_image_url: 'https://text-to-image.hoyoverse.workers.dev/',
+	watermark_fg: 'fff',
+	watermark_bg: '000',
+	watermark_shadow_offset: 2
+};
+
 
 async function build_request(request) {
 	let dstUrl = new URL(request.url);
 
-	dstUrl.host = request.headers.has('x-host') ? request.headers.get('x-host') : aws_base_url;
+	dstUrl.host = request.headers.has('x-host') ? request.headers.get('x-host') : globalConfiguration.aws_base_url;
 	dstUrl.search = '';
 	// dstUrl.pathname = url.pathname.substring('/imgprocessingtest'.length)
 
 
 	let req = await aws.sign(dstUrl.toString());
-	dstUrl.host = self_host;
+	dstUrl.host = globalConfiguration.self_host;
 	return new Request(dstUrl.toString(), { headers: req.headers });
 }
 
 async function getDimensions(req) {
-	let s_data = await fetch(req);
-	let s_buffer = await s_data.arrayBuffer();
-	return sizeOf(Buffer.from(s_buffer));
+	// return {
+	// 	width: 4096,
+	// 	height: 4096
+	// };
+	let response = await fetch(req);
+	let buffer = await response.arrayBuffer();
+	return sizeOf(Buffer.from(buffer));
+
+}
+
+function parseArg(arg, default_conf) {
+	let str_arg = arg.reduce((obj, str) => {
+		let pair = str.split('_');
+		if (pair.length === 2) {
+			obj[pair[0]] = pair[1];
+		}
+		return obj;
+	}, {});
+	if (!default_conf) return str_arg;
+
+	let conf_raw = { ...default_conf, ...str_arg };
+
+	let numberFields = Object.keys(default_conf).filter(k => typeof default_conf[k] === 'number');
+
+	return numberFields.reduce((obj, field) => {
+		obj[field] = Number(obj[field]);
+		return obj;
+	}, conf_raw);
+
+}
+
+async function parseWaterMark(arg, ctx) {
+	let { req } = ctx;
+	console.log('parseWaterMark');
+	const default_conf = {
+		t: 100,//透明度。[0,100]
+		g: 'se',//位置。nw:左上north:中上ne:右上west:左中center:中部east:右中sw:左下south:中下se
+		x: 10,//水平边距。[0,4096]
+		y: 10,//垂直边距。[0,4096]
+		// voffset: 0,//中线垂直偏移。[-1000,1000]
+		// fill: 0,//1:将图片水印或文字水印铺满原图。0(默认值):不将图片水印或文字水印铺满全图。
+		// padx: 0,//水印平铺时单个水印间的水平间隔。仅在水印平铺开启时有效。[0,4096]
+		// pady: 0,//水印平铺时单个水印间的垂直间隔。仅在水印平铺开启时有效。[0,4096]
+
+		//text:'required'
+		// type: 'ZHJvaWRzYW5zZmFsbGJhY2s',//字体,默认DroidSansFallback
+		color: '000000',//水印的文字颜色
+		size: 40,//文字大小
+		shadow: 0,//阴影透明度
+		rotate: 0//字顺时针旋转角度
+	};
+
+
+	let conf = parseArg(arg, default_conf);
+
+	if (!conf.text || conf.t === 0) return {};
+
+	let base = {};
+
+	if (['nw', 'west', 'sw'].includes(conf.g)) {
+		base.left = conf.x;
+	} else if (['ne', 'east', 'se'].includes(conf.g)) {
+		base.right = conf.x;
+	}
+
+	if (['nw', 'north', 'ne'].includes(conf.g)) {
+		base.top = conf.y;
+	} else if (['sw', 'south', 'se'].includes(conf.g)) {
+		base.bottom = conf.y;
+	}
+	if (conf.rotate / 90 % 2) base.width = conf.size * 0.9;
+	else base.height = conf.size * 0.9;
+	return {
+		draw: [{
+			url: `${globalConfiguration.text_to_image_url}${conf.text}?size=${conf.size * 2}&color=${conf.color}&shadow=${conf.shadow}&offset=${globalConfiguration.watermark_shadow_offset * 2}`,
+			...base,
+			opacity: conf.t / 100,
+			rotate: conf.rotate
+		}]
+	};
+}
+
+async function parseResize(arg, { req }) {
+	console.log('parseResize');
+	const default_conf = {
+		m: 'lfit',//缩放的模式。lfit、mfit、fill、pad、fixed
+		w: 0,//宽度。[1,16384]
+		h: 0,//高度。
+		l: 0,//指定目标缩放图的最长边。
+		s: 0,//指定目标缩放图的最短边。
+		limit: 1,//当目标图片分辨率大于原图分辨率时,是否进行缩放。
+		color: 'FFFFFF',//当缩放模式选择为pad(缩放填充)时,可以设置填充的颜色。
+		p: 0//按百分比缩放图片。
+	};
+	let conf = parseArg(arg, default_conf);
+	if (conf.w || conf.h || conf.l || conf.s) {
+		let base = {
+			width: conf.w === 0 ? undefined : conf.w,
+			height: conf.h === 0 ? undefined : conf.h,
+			fit: 'scale-down'
+		};
+
+		switch (conf.m) {
+			case 'lfit':
+				base.fit = conf.limit === 1 ? 'scale-down' : 'contain';
+				break;
+			case 'mfit':
+			case 'fill':
+				base.fit = conf.limit === 1 ? 'crop' : 'contain';
+				break;
+			case 'pad':
+				base.fit = 'pad';
+				base.background = conf.color;
+				break;
+			default:
+				break;
+		}
+
+		if (conf.l || conf.s) {
+			let dimensions = await getDimensions(req);
+			if (conf.s) {
+				if (dimensions.width < dimensions.height) {
+					base.width = conf.s;
+					base.height = undefined;
+				} else {
+					base.height = conf.s;
+					base.width = undefined;
+				}
+			} else {
+				if (dimensions.width < dimensions.height) {
+					base.height = conf.l;
+					base.width = undefined;
+				} else {
+					base.width = conf.l;
+					base.height = undefined;
+				}
+			}
+		}
+		return base;
+	}
+	if (conf.p) {
+		let dimensions = await getDimensions(req);
+		return {
+			fit: 'scale-down',
+			height: Math.round(dimensions.height * Number(conf.p) / 100)
+		};
+	}
+	return {};
+}
+
+async function parseQuality(arg, ctx) {
+	console.log('parseQuality');
+	let conf = parseArg(arg);
+	return {
+		quality: conf.q
+	};
+}
+
+async function parseFormat(args, ctx) {
+	console.log('parseFormat');
+	switch (args[0]) {
+		case 'jpg':
+			return { format: 'jpeg' };
+		case 'gif':
+			return {};
+		default:
+			return { format: args[0] };
+	}
+}
+
+async function parseCrop(arg, ctx) {
+	console.log('parseCrop');
+	const default_conf = {
+		w: 0,//宽度。
+		h: 0,//高度。
+		x: 0,//指定裁剪起点横坐标(默认左上角为原点)。
+		y: 0,//指定裁剪起点纵坐标(默认左上角为原点)。
+		g: 'nw'//裁剪原点位置。nw:左上,north:中上,ne:右上,west:左中,center:中部,east:右中,sw:左下,south:中下,se:右下
+	};
+	let conf = parseArg(arg, default_conf);
+	return {
+		trim: {
+			width: conf.w,
+			height: conf.h,
+			left: conf.x,
+			top: conf.y
+		}
+	};
 }
 
+const processMap = {
+	'watermark': parseWaterMark,
+	'resize': parseResize,
+	'quality': parseQuality,
+	'format': parseFormat,
+	'crop': parseCrop
+};
+
 export default {
-	async fetch(request, env, ctx) {
+	async fetch(request, _env, _ctx) {
 
 		let req = await build_request(request);
 
@@ -48,124 +245,51 @@ export default {
 		}
 
 
-		let map = {};
-		let image = {};
-		let draw_config = {
-			url: `${text_to_image_url}ICBIb1lvTEFCQNGO0L3RjNC60LA8Mw==`
-		};
-
+		let process_arg = [];
 		let url = new URL(request.url);
-		let enable_process = false;
-		let enable_watermark = false;
 		for (const [key, value] of url.searchParams) {
 			if (key === 'x-oss-process') {
-				let processes = value.split('/');
-				processes.forEach(process => {
-					let args = process.split(',');
-					switch (args[0]) {
-						case 'image':
-							enable_process = true;
-							break;
-						case 'format':
-							if (args[1] === 'jpg') {
-								image.format = 'jpeg';
-							} else if (args[1] !== 'gif') {
-								image.format = args[1];
-							}
-							break;
-						// case 'auto-orient': //忽略此参数
-						//   config.rotate = Number(args[1]);
-						//   break;
-						case 'watermark':
-							enable_watermark = true;
-						case 'quality':
-						case 'resize':
-							args.forEach(str => {
-								let pair = str.split('_');
-								if (pair.length === 2) {
-									map[pair[0]] = pair[1];
-								}
-							});
-							break;
-
-					}
-				});
+				let processes_raw = value.split('/');
+				process_arg = processes_raw.map(process => process.split(','));
 			}
 		}
 
-		let watermark_text = '';
-		for (let k in map) {
-			switch (k) {
-				//watermark
-				case 'text':
-					watermark_text = map[k];
-					break;
-				case 'x':
-					draw_config.right = Number(map['x']);
-					break;
-				case 'y':
-					draw_config.bottom = Number(map['y']);
-					break;
-				case 'size':
-					draw_config.height = Number(map['size']);
-					break;
-				case 't':
-					draw_config.opacity = Number(map['t']) / 100;
-					break;
-				//resize
-				case 'w':
-					image.width = Number(map['w']);
-					break;
-				case 'h':
-					image.height = Number(map['h']);
-					break;
-				case 'm':
-					image.fit = 'scale-down';
-					break;
-				case 's':
-					image.fit = 'scale-down';
-					// image.fit = "scale-down";
-					let short = Number(map['s']);
-					let s_dimensions = await getDimensions(req);
-					if (s_dimensions.width < s_dimensions.height) {
-						image.width = short;
-					} else {
-						image.height = short;
-					}
-					break;
-				case 'p':
-					image.fit = 'scale-down';
-					let p_dimensions = await getDimensions(req);
-					image.height = Math.round(p_dimensions.height * Number(map['p']) / 100);
-					break;
-				//quality
-				case 'q':
-					image.quality = Number(map['q']);
-					break;
+		//无image参数,返回原图
+		if (!process_arg.some(processes => processes[0] === 'image')) {
+			return await fetch(req);
+		}
 
+		//过滤不可用选项
+		process_arg = process_arg.filter(process => !!processMap[process[0]]);
+		let ctx = { req, process_arg };
+		//解析参数并构造图像变换选项
+		let image = (await Promise.all(
+				process_arg.map(process => {
+					let process_func = processMap[process[0]];
+					return process_func(process.slice(1), ctx);
+				}))
+		).reduce((a, b) => ({ ...a, ...b }), {});
+		//Crop与Resize综合处理
+		let resize_index = ctx.process_arg.findIndex(item => item[0] === 'resize');
+		let crop_index = ctx.process_arg.findIndex(item => item[0] === 'crop');
+		if (resize_index !== -1 && crop_index !== -1) {
+			let resize_conf = parseArg(ctx.process_arg[resize_index], { p: 0 });
+			if (resize_conf.p) {
+				if (resize_index < crop_index) {
+					image.height = image.trim.height;
+					Object.keys(image.trim).forEach(key => image.trim[key] /= resize_conf.p / 100);
+				} else {
+					image.height = image.trim.height * resize_conf.p / 100;
+				}
 			}
 		}
-		//水印处理
-		if (enable_watermark) {
-			draw_config.url = `${text_to_image_url}${watermark_text}?size=${draw_config.height}&color=${watermark_fg}`;
-			let draw_config_bg = { ...draw_config };
-			draw_config_bg.right -= 3;
-			draw_config_bg.bottom -= 3;
-			draw_config_bg.url = `${text_to_image_url}${watermark_text}?size=${draw_config.height}&color=${watermark_bg}`;
-			image.draw = [draw_config_bg, draw_config];
-		}
 
+		console.log('image:', JSON.stringify(image));
 
-		//无image参数,返回原图
-		if (!enable_process) {
-			return await fetch(req);
-		}
 
 		//获取返回经过处理的图片
 		let originalResponse = await fetch(req, {
-			cf: {
-				image
-			}
+			cf: { image }
 		});
 
 		//GIF图片过大,处理失败,返回原图