以说,每一个“使用计算机的人”都需要在某个时间点调整图像的大小。MacOS的预览版可以做到,WindowsPowerToys也可以。
本文使用Python来调整图像大小,幸运的是,图像处理和命令行工具是Python的两个特长。
本文旨在向你展示三件事:
我们要构建的命令行程序可以一次调整一个或多个图像文件的大小。
在这个例子中,我们将创建我们自己的图像,而不是找到一个真正的图像来操纵。
为什么?事实上,创造图像是一个很好的方式来说明一个图像实际上是什么。这个调整大小的程序在Instagram上也同样适用。
那么,什么是图像?在Python数据术语中,图像是int元组的列表。
image=list[list[tuple[*int, float]]]
NumPy的定义是一个二维形状数组 (h, w, 4),其中h表示高的像素数(上下),w表示宽的像素数(从左到右)。
换句话说,图像是像素列表(行)的列表(整个图像)。每个像素由3个整数和1个可选浮点数组成:红色通道、绿色通道、蓝色通道、alpha(浮点可选)。红色、绿色、蓝色通道(RGB)的值从0到255。
从现在开始,我们将讨论没有alpha通道的彩色图像,以保持简单。Alpha是像素的透明度。图像也只能有一个值从0到255的通道。这就是灰度图像,也就是黑白图像。在这里我们使用彩色图像!
import matplotlib as plt
pixel: tuple=(200, 100, 150)
plt.imshow([[list(pixel)]])
Python完全能够创建图像。要显示它,我将使用matplotlib库,你可以使用它安装:
pip install matplotlib
创建像素:
from dataclasses import dataclass
@dataclass
class Pixel:
red: int
green: int
blue: int
# alpha: float=1
pixel=Pixel(255,0,0)
pixel
# returns:
# Pixel(red=255, green=0, blue=0, alpha=1)
创建图像:
from __future__ import annotations
from dataclasses import dataclass, astuple
from itertools import cycle
from typing import List
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
@dataclass
class Pixel:
red: int
green: int
blue: int
# alpha: float=1
pixel=Pixel(255,0,0)
pixel
marigold: Pixel=Pixel(234,162,33)
red: Pixel=Pixel(255,0,0)
Image=List[List[Pixel]]
def create_image(*colors: Pixel, blocksize: int=10, squaresize: int=9) -> Image:
""" 用可配置的像素块制作一个正方形图像(宽度和高度相同).
Args:
colors (Pixel): 可迭代的颜色呈现顺序的参数。
blocksize (int, optional): [description]. 默认10.
squaresize (int, optional): [description]. 默认9.
Returns:
Image: 一幅漂亮的正方形图片!
"""
img: list=[]
colors=cycle(colors)
for row in range(squaresize):
row: list=[]
for col in range(squaresize):
color=next(colors) # 设置颜色
for _ in range(blocksize):
values: list[int]=list(astuple(color))
row.append(values)
[img.append(row) for _ in range(squaresize)] # 创建行高
return img
if __name__=='__main__':
image=create_image(marigold, red)
plt.imshow(image)
这就是渲染的图像。在背后,数据是这样的:
[[[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[234, 162, 33],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
[234, 162, 33],
...
现在我们有了一个图像,让我们调整它的大小!
在Python中编写调整图像大小的算法实际上有很多的工作量。
在图像处理算法中有很多内容,有些人为此贡献了十分多的工作。例如重采样——在缩小后的图像中使用一个像素来代表周围的高分辨率像素。图像处理是一个巨大的话题。如果你想亲眼看看,看看Pillow的Image.py,它在路径path/to/site-packages/PIL中。
这中间还有一些优化,比如抗锯齿和减少间隙…这里的内容非常多。我们是站在巨人的肩膀上,可以用一行代码来解决我们的问题。
如果你有兴趣了解更多有关处理图像时幕后发生的事情,我鼓励你更多地查看“机器视觉”主题!这绝对是一个蓬勃发展的领域。
做得足够好,就会有很多公司愿意为你的计算机视觉专业知识付出最高的代价。自动驾驶,IOT,监视,你命名它;所有基本上依赖于处理图片(通常在Python或C++)。
一个很好的起点是查看scikit image。
OpenCV可以用来作图像处理。他使用C++编写并移植到了Python
import cv2
def resize(fp: str, scale: Union[float, int]) -> np.ndarray:
""" 调整图像大小,保持其比例
Args:
fp (str): 图像文件的路径参数
scale (Union[float, int]): 百分比作为参数。如:53
Returns:
image (np.ndarray): 按比例缩小的图片
"""
_scale=lambda dim, s: int(dim * s / 100)
im: np.ndarray=cv2.imread(fp)
width, height, channels=im.shape
new_width: int=_scale(width, scale)
new_height: int=_scale(height, scale)
new_dim: tuple=(new_width, new_height)
return cv2.resize(src=im, dsize=new_dim, interpolation=cv2.INTER_LINEAR)
interpolation参数的选项是cv2包中提供的flags之一:
INTER_NEAREST – 近邻插值
INTER_LINEAR – 双线性插值(默认使用)
INTER_AREA – 利用像素区域关系重新采样。它可能是图像抽取的首选方法。但是当图像被缩放时,它类似于INTER_NEAREST方法。
INTER_CUBIC – 一个大于4×4像素邻域的双三次插值
INTER_LANCZOS4 – 一个大于8×8像素邻域的Lanczos插值
返回后:
resized=resize("checkers.jpg", 50)
print(resized.shape)
plt.imshow(resized) # 也可以使用 cv2.imshow("name", image)
它做了我们所期望的。图像从900像素高,900像素宽,到450×450(仍然有三个颜色通道)。因为Jupyter Lab的matplotlib着色,上面的屏幕截图看起来不太好。
pillow库在Image类上有一个调整大小的方法。它的参数是:
size: (width, height)
resample: 默认为BICUBIC. 重采样算法需要的参数。
box: 默认为None。为一个4元组,定义了在参数(0,0,宽度,高度)内工作的图像矩形。
reducing_gap: 默认为None。重新采样优化算法,使输出看起来更好。
以下是函数:
from PIL import Image
def resize(fp: str, scale: Union[float, int]) -> np.ndarray:
""" 调整图像大小,保持其比例
Args:
fp (str): 图像文件的路径参数
scale (Union[float, int]): 百分比作为参数。如:53
Returns:
image (np.ndarray): 按比例缩小的图片
"""
_scale=lambda dim, s: int(dim * s / 100)
im=Image.open(fp)
width, height=im.size
new_width: int=_scale(width, scale)
new_height: int=_scale(height, scale)
new_dim: tuple=(new_width, new_height)
return im.resize(new_dim)
使用Pillow 的函数与OpenCV非常相似。唯一的区别是PIL.Image.Image类具有用于访问图像(宽度、高度)的属性大小。
结果是:
resized=resize("checkers.jpg", 30.5)
print(resized.size)
resized.show("resized image", resized)
请注意show方法如何打开操作系统的默认程序以查看图像的文件类型。
现在我们有了一个调整图像大小的函数,现在是时候让它有一个运行调整大小的用户界面了。
调整一个图像的大小是可以的。但我们希望能够批量处理图像。
我们将要构建的接口将是最简单的接口:命令行实用程序。
Pallets项目是Flask背后的天才社区,是一个Jinja模板引擎:Click(https://click.palletsprojects.com/en/7.x/。)
pip install click
Click是一个用于制作命令行程序的库。这比使用普通的argparse或在if __name__=='__main__':中启动一些if-then逻辑要好得多。所以,我们将使用Click来装饰我们的图像调整器。
下面是从命令行调整图像大小的完整脚本!
""" resize.py
"""
from __future__ import annotations
import os
import glob
from pathlib import Path
import sys
import click
from PIL import Image
"""
文档:
https://pillow.readthedocs.io/en/5.1.x/handbook/image-file-formats.html
"""
SUPPORTED_FILE_TYPES: list[str]=[".jpg", ".png"]
def name_file(fp: Path, suffix) -> str:
return f"{fp.stem}{suffix}{fp.suffix}"
def resize(fp: str, scale: Union[float, int]) -> Image:
""" 调整图像大小,保持其比例
Args:
fp (str): 图像文件的路径参数
scale (Union[float, int]): 百分比作为参数。如:53
Returns:
image (np.ndarray): 按比例缩小的图片
"""
_scale=lambda dim, s: int(dim * s / 100)
im: PIL.Image.Image=Image.open(fp)
width, height=im.size
new_width: int=_scale(width, scale)
new_height: int=_scale(height, scale)
new_dim: tuple=(new_width, new_height)
return im.resize(new_dim)
@click.command()
@click.option("-p", "--pattern")
@click.option("-s", "--scale", default=50, help="Percent as whole number to scale. eg. 40")
@click.option("-q", "--quiet", default=False, is_flag=True, help="Suppresses stdout.")
def main(pattern: str, scale: int, quiet: bool):
for image in (images :=Path().glob(pattern)):
if image.suffix not in SUPPORTED_FILE_TYPES:
continue
im=resize(image, scale)
nw, nh=im.size
suffix: str=f"_{scale}_{nw}x{nh}"
resize_name: str=name_file(image, suffix)
_dir: Path=image.absolute().parent
im.save(_dir / resize_name)
if not quiet:
print(
f"resized image saved to {resize_name}.")
if images==[]:
print(f"No images found at search pattern '{pattern}'.")
return
if __name__=='__main__':
main()
命令行程序从入口点函数main运行。参数通过传递给click.option选项:
从命令行运行程序:
python resize.py -s 35 -p "./*jpg"
结果:
$ py resize.py -p "checkers.jpg" -s 90
resized image saved to checkers_90_810x810.jpg.
正在检查文件夹:
$ ls -lh checkers*
-rw-r--r-- 1 nicho 197609 362K Aug 15 13:13 checkers.jpg
-rw-r--r-- 1 nicho 197609 231K Aug 15 23:56 checkers_90_810x810.jpg
不错!所以程序缩小了图像,给了它一个描述性的标签,我们可以看到文件大小从362KB到231KB!
为了查看程序同时处理多个文件,我们将再次运行它:
$ py resize.py --pattern="checkers*" --scale=20
resized image saved to checkers_20_180x180.jpg.
resized image saved to checkers_90_810x810_20_162x162.jpg.
文件系统输出:
$ ll -h checkers*
-rw-r--r-- 1 nicho 197609 362K Aug 15 13:13 checkers.jpg
-rw-r--r-- 1 nicho 197609 1.8K Aug 16 00:23 checkers_20_180x180.jpg
-rw-r--r-- 1 nicho 197609 231K Aug 15 23:56 checkers_90_810x810.jpg
-rw-r--r-- 1 nicho 197609 1.8K Aug 16 00:23 checkers_90_810x810_20_162x162.jpg
只要匹配到了模式,递归可以处理任意数量的图像。
Click 是一个神奇的工具。它可以包装一个函数并在一个模块中以“正常的方式”从一个if __name__=='__main__'语句运行。(实际上,它甚至不需要这样做;你只需定义和装饰要运行的函数即可),但它真正的亮点在于将脚本作为包安装。
这是通过Python附带的setuptools库完成的。
这是我的setup.py.
from setuptools import setup
setup(
name='resize',
version='0.0.1',
py_modules=['resize'],
install_requires=[
'click',
'pillow',
],
entry_points='''
[console_scripts]
resize=resize:main
'''
)
使用以下命令生成可执行文件/包装包:
pip install -e .
现在,你可以在不使用python命令的情况下调用脚本。另外,如果你将新的可执行文件添加到路径中的文件夹中,你可以从计算机上的任何位置调用此程序,如resize -p *jpg -s 75
本教程进行了大量的研究:
请记住,编写代码可能需要数小时或数天。但它只需几毫秒就可以运行。你制作的程序不必很大。任何一件能节省你的时间或让你产生更多产出的东西,都有可能为你的余生服务!
作放大镜,首先必须要掌握常见的六大尺寸值。如果这六个值不太熟悉的话,可能有点吃力,不过下图已经分析的很清楚了,应该能看懂。需要注意的一点是,除了鼠标位置是按照窗口来进行定位的,如图绿色画线,其他四项尺寸值是按照父元素——子元素关系来计算尺寸,如body和div1,div1和div2 。
offsetLeft和style.left主要有三点不同:
现在来分析:当放大镜(鼠标)在小图片上移动 x 距离时,大图片移动的距离Y是多少呢?
其实根据 等比 关系,有图中的关系:
下图中关系式,其实就是由核心公式转化而来:X/?=B/D=A/C.
(为了方便,只讨论单方向和横轴方向距离)
X:放大镜向左移动的距离;
?:大图片向右(反方向)移动的距离;
A:放大镜的宽;
B:小容器的宽,为了兼容,实际为mark的宽,不过与小容器宽相等的;
C:大容器的宽;
D:大图片的宽;
图中数字代表距离,则x的值应该如下计算:
上面就是放大镜核心原理。明白了原理后,对于放大镜的移动范围,浏览器的兼容性等细节再进行优化可以咯。
代码还是要贴上来的:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>放大镜</title> <style> * { margin: 0; padding: 0 } // 最外层,包裹所有元素 #demo { display: block; width: 400px; height: 255px; margin: 50px; position: relative; border: 1px solid #ccc; } // 小容器 #small-box { position: relative; z-index: 1; } // 放大镜 #float-box { display: none; width: 160px; height: 120px; position: absolute; background: #ffffcc; border: 1px solid #ccc; filter: alpha(opacity=50); opacity: 0.5; } // 为了兼容IE,把添加在小图片的特性全部移到mark #mark { position: absolute; display: block; width: 400px; height: 255px; background-color: #fff; filter: alpha(opacity=0); opacity: 0; z-index: 10; } // 大容器 #big-box { display: none; position: absolute; top: 0; left: 460px; width: 400px; height: 300px; overflow: hidden; border: 1px solid #ccc; z-index: 1; ; } // 大图片 #big-box img { position: absolute; z-index: 5 } </style> <script> //页面加载完毕后执行 window.onload=function() { var objDemo=document.getElementById("demo"); var objSmallBox=document.getElementById("small-box"); var objMark=document.getElementById("mark"); var objFloatBox=document.getElementById("float-box"); var objBigBox=document.getElementById("big-box"); var objBigBoxImage=objBigBox.getElementsByTagName("img")[0]; // 鼠标移入时触发的事件 objMark.onmouseover=function() { objFloatBox.style.display="block" objBigBox.style.display="block" } // 鼠标离开时触发的事件 objMark.onmouseout=function() { objFloatBox.style.display="none" objBigBox.style.display="none" } // 鼠标在小图片上移动时触发的事件 objMark.onmousemove=function(ev) { // 兼容浏览器 var _event=ev || window.event; // 鼠标移动的 变化距离 var left=_event.clientX - objDemo.offsetLeft - objSmallBox.offsetLeft - objFloatBox.offsetWidth / 2; var top=_event.clientY - objDemo.offsetTop - objSmallBox.offsetTop - objFloatBox.offsetHeight / 2; // 把放大镜限制在小容器内 if (left < 0) { left=0; } else if (left > (objMark.offsetWidth - objFloatBox.offsetWidth)) { left=objMark.offsetWidth - objFloatBox.offsetWidth; } if (top < 0) { top=0; } else if (top > (objMark.offsetHeight - objFloatBox.offsetHeight)) { top=objMark.offsetHeight - objFloatBox.offsetHeight; } //放大镜跟随鼠标发生移动后的当前位置 objFloatBox.style.left=left + "px"; objFloatBox.style.top=top + "px"; //发生移动后,产生的 等比例 关系。 var percentX=left / (objMark.offsetWidth - objFloatBox.offsetWidth); var percentY=top / (objMark.offsetHeight - objFloatBox.offsetHeight); //利用等比例关系计算 大图片 反向 移动的距离 objBigBoxImage.style.left=-percentX * (objBigBoxImage.offsetWidth - objBigBox.offsetWidth) + "px"; objBigBoxImage.style.top=-percentY * (objBigBoxImage.offsetHeight - objBigBox.offsetHeight) + "px"; } } </script> </head> <body> <div id="demo"> <div id="small-box"> <div id="mark"></div> <div id="float-box"></div> <img src="macbook-small.jpg" /> // 这张是小图片。 </div> <div id="big-box"> <img src="macbook-big.jpg" /> // 这张是大图片。 </div> </div> </body> </html>
这张是小图片,可以下载后置于源码中使用。
这张是大图片,可以下载后置于源码中使用。
了点时间写的,蛮长时间了。个人很喜欢,一段很简单的代码,却能够实现很多功能。(因为代码文字呈现没有格式,难以阅读,以后小编提供的代码都以截图方式呈现,底部有源码链接)。
到底多简单,先来看代码
基于jQuery
基于jQuery
拖拽实例图:
拖拽实例图
将代码剥离,只要写5行就可以实现拖拽了,是不是很简单:
调用方式
放大、缩小
我们给拖拽增加点功能,支持放大、缩小,先看实例图:
放大、缩小
将代码剥离,原先的代码保留不变,增加一个绑定事件:
放大、缩小
这样来实现放大、缩小、拖拽是不是很简单,还能实现很多其他效果,大家慢慢领悟。
原理分析:
放大、缩小、拖拽都离不开在网页上拖动鼠标,对于前端来说就是 document 的 mousemove,当鼠标在网页上移动的时候,无时无刻不在触发 mousemove 事件,当鼠标触发事件时,什么时候需要执行我们特定的操作,这就是我们要做的了。我在 mousemove 中增加了几个对象来判定是否进行操作:
move:是否执行触发操作
move_target:操作的元素对象
move_target.posix:操作对象的坐标
call_down:mousemove的时候的回调函数,传回来的this指向document
call_up:当鼠标弹起的时候执行的回调函数,传回来的this指向document
小提示:
简单的操作,只需要设定 move_target 对象,设置 move_target 的时候不要忘记了 move_target.posix 哦;
复杂的操作可以通过call_down、call_up进行回调操作,这个时候是可以不用设置 move_target 对象的
深入研究
拖拽和放大、缩小实现了,但是有个问题,当我们鼠标点击并滑动的时候,是会选中文本的,为了避免这个问题,大家可以自行百度
css 阻止文本选中
css 阻止文本选中
网页的放大、缩小、拖拽事件就研究到这里了,小编不再对如何拓展进行深入讲解,一切靠大家自行研究,权当课后作业了。~~
源码链接地址:
http://orzcss.com/posts/d554a392/
本文内容均属个人原创作品,转载此文章须附上出处及原文链接。
加关注,定时推送,互动精彩多,若你有更好的见解,欢迎留言探讨!
*请认真填写需求信息,我们会在24小时内与您取得联系。