【办公自动化】Python图像操作术

现在的我们,几乎每时每刻都在遇到数字图片文件,处理这些文件当然可以用诸如Adobe Photoshop之类的专业软件。只是如果遇到了大量图像的编辑工作,尤其是某些比较简单粗暴的操作,手动编辑将是一次漫长而枯燥的苦旅。

不过我们可以使用Python和第三方模块Pillow,该模块包含了一些函数,如裁剪图像、调整大小、编辑图像等。有了这种能力,我们就可以轻松处理成千上万张图片文件。不过开始本文之前,记得用 pip install pillow 安装这一模块(要是安装Haskell包时也能这么轻松就好了┏(゜ロ゜;)┛):

$ pip install pillow
Collecting pillow
  Downloading Pillow-8.3.1-1-cp39-cp39-win_amd64.whl (3.2 MB)
     |████████████████████████████████| 3.2 MB 356 kB/s 
Installing collected packages: pillow
Successfully installed pillow-8.3.1

1. 计算机图像基础

1.1 颜色和RGBA值

像素是计算机屏幕上能够显示一种颜色的最小点,计算机程序通常把图像分解为一系列像素点,因此图像就是像素的集合。图像中的每种颜色都被表示为一个RGBA值,或者说一组四个取值范围为 [0, 255] 的整数,分别指定颜色中的红、绿、蓝和 alpha 透明度的值。图像中的每个像素都有一个表示颜色的RGBA值,用以准确告诉计算机屏幕(上对应坐标的像素)该显示哪种颜色,alpha 值则决定了,当图像显示在屏幕上、遮住了背景图像或桌面壁纸时,透过这张图像我们可以看到多少背景。

屏幕分辨率是指纵横向上的像素点数,单位是px。屏幕分辨率是确定计算机屏幕上显示多少信息的设置,以水平和垂直像素来衡量。就相同大小的屏幕而言,当屏幕分辨率低时(例如 640 x 480),在屏幕上显示的像素少,单个像素尺寸比较大。屏幕分辨率高时(例如 1600 x 1200),在屏幕上显示的像素多,单个像素尺寸比较小。
显示分辨率就是屏幕上显示的像素个数,分辨率160×128的意思是水平方向含有像素数为160个,垂直方向像素数128个。屏幕尺寸一样的情况下,分辨率越高,显示效果就越精细和细腻。

Pillow中的RGBA值是一个四个整数的元组,如红色表示为 (255, 0, 0, 255)(即颜色中红色的值最大、没有绿和蓝,alpha 值最大即完全不透明),绿色表示为 (0, 255, 0, 255) ,蓝色是 (0, 0, 255, 255) ,白色是 (255, 255, 255, 255) 即各种颜色的组合,黑色没有任何颜色,是 (0, 0, 0, 255)

提示1:如果一种颜色的 alpha 值为 0 ,则不论RGB值是什么,该颜色都不可见,是透明的。透明的红色等同于透明的黑色。计算机体系结构中的透明性概念,是否取自于此呢?

提示2:小时候肯定学过三原色,混合红黄蓝三种颜色可以得到其他颜色,比如混合黄色和蓝色可以得到绿色。不过这是一种不严谨的说法。

准确来说,要区分色光三原色(红 red 、绿 green 、蓝 blue)和色料三原色(红黄蓝或者更准确的是,品红 magenta 、黄 yellow 、青 cyan),两者分别对应色光加色模型色料减色模型
在这里插入图片描述
由于光的物理使用加色模型,在计算机程序中也使用RGB值表示颜色,这样计算机屏幕发出的红光、绿光和蓝光就可以组合形成其他颜色。

下表是一些标准颜色名称和对应的RGBA值:

名称RGBA值名称RGBA值
White(255, 255, 255, 255)Black(0, 0, 0, 255)
Red(255, 0, 0, 255)Gray(128, 128, 128, 255)
Green(0, 255, 0, 255)Yellow(255, 255, 0, 255)
Blue(0, 0, 255, 255)Purple(128, 0, 128, 255)

为了减少记忆负担,不用记住那麽多颜色的RGBA值,Pillow提供了 ImageColor.getcolor() 函数,接受一个不区分大小写的颜色名称字符串作为第一个参数(Pillow支持超过100种标准颜色名称),字符串 'RGBA' 作为第二个参数,返回一个RGBA四元组:

>>> from PIL import ImageColor
>>> ImageColor.getcolor('red', 'RGBA')
(255, 0, 0, 255)
>>> ImageColor.getcolor('green', 'RGBA') 
(0, 128, 0, 255)
>>> ImageColor.getcolor('BLUE', 'RGBA')  
(0, 0, 255, 255)
>>> ImageColor.getcolor('White', 'RGBA') 
(255, 255, 255, 255)
>>> ImageColor.getcolor('bLack', 'RGBA') 
(0, 0, 0, 255)
>>> ImageColor.getcolor('grey', 'RGBA')  
(128, 128, 128, 255) 
>>> ImageColor.getcolor('yellow', 'RGBA') 
(255, 255, 0, 255)
>>> ImageColor.getcolor('chocolate', 'RGBA') 
(210, 105, 30, 255)
>>> ImageColor.getcolor('CornflowerBlue', 'RGBA') 
(100, 149, 237, 255)

为什么是从PIL中导入而不是从Pillow?因为Pillow的模块名称就是PIL,以保持与老模块 Python Imaging Library 的向后兼容。同时由于Pillow作者的设计方式,我们必须使用 from PIL import xxx 这种形式的 import 语句。所以,代码中不会出现 import Pillowfrom Pillow import xxx 或者简单地 import PIL

1.2 坐标和Box元组

图像像素的位置用 (x, y) 坐标指定,分别表示像素在图像中的水平和垂直位置。原点位于图像最左上角处,用 (0, 0) 指定。x 坐标以原点处为零,从左至右不断增加;y 坐标以原点处为零,从上到下不断增加:
在这里插入图片描述

很多Pillow函数或方法需要一个矩形元组参数,即需要一个四个整数坐标的元组 (leftmost, topmost, rightmost, downmost),以表示图像中的一个矩形区域。更准确的说是表示矩形区域 leftmost <= x < rightmost, topmost <= y < downmost ,左闭右开,包括左和顶坐标、但不包括右和底坐标。如矩形元组 (3, 1, 9, 6) 表示下图黑色矩形的所有像素:
在这里插入图片描述


2. 用Pillow操作图像

现在知道了Pillow中颜色和坐标的定义,就可以开始用Pillow处理图像了。以下图 Zophie.png 为例:

本节只是这些函数的简单介绍,具体使用方法可以用 help(ImageObject.method)help(Image.method) 进行查看。

2.1 加载图像、保存图像

加载图像需要从Pillow中导入 Image ,调用 Image.open() 并传入图像路径字符串,再保存到类似 catIm 这样的变量中:

>>> from PIL import Image
>>> catIm = Image.open('sources/zophie.png') 

如果图像文件不在当前工作目录,可以像上面一样传入路径字符串,也可以调用 os.chdir() 函数,将工作目录转为包含图像文件的文件夹:

>>> import os 
>>> os.chdir('C:\\folder_with_image_file')

Image.open() 函数将返回一个 Image 对象数据模型,是Pillow将图像表示为Python值的中介方法。调用 Image.open() 并传入文件路径字符串,就可以从一个任意格式的图像文件加载一个 Image 对象。对图像进行的所有修改,如旋转、调整大小、裁剪、绘画和其他图像操作,均能通过调用这一 Image 对象上的方法来完成。之后通过 save() 方法,将对 Image 对象的所有更改保存到任意格式的图像文件中。

2.2 处理 Image 数据类型、创建空白图像

在文件资源管理器中,我们可以看到一张图像文件的诸多信息:

加载得到的 Image 对象也有这些有用的属性,以提供加载的图像文件的基本信息,如宽度、高度、分辨率、文件名和图像格式。下面的代码中,能看到该 Image 对象的大小 size 属性是一个二元组,包含该图像的宽度和高度像素数,将其分别赋给 widthheight 变量,以便分别进行访问;文件名 filename 属性描述了传入文件的名称;格式 formatformat_description 属性是字符串,描述了原始文件的图像格式。最后调用 Image.save() 方法,将图像保存为 zophie.jpg ——Pillow看到文件扩展名是 .jpg ,会自动使用JPEG图像格式保存图像

>>> catIm.size
(816, 1088)
>>> width, height = catIm.size
>>> width
816
>>> height
1088
>>> catIm.filename
'sources/zophie.png'
>>> catIm.format
'PNG'
>>> catIm.format_description
'Portable network graphics'
>>> catIm.save('zophie.jpg')

Pillow还提供了 Image.new() 函数,返回一个表示空白图像的 Image 对象,具体方法参数如下:

  • 字符串 'RGBA' ,将颜色模式设置为RGBA(或者其他模式);
  • 表示大小的两个整数的元组,作为新图像的宽度和高度;
  • 图像开始使用的背景颜色,一个表示RGBA值的四整数元组,用 ImageColor.getcolor() 的返回值作为这一参数是很合适的,或者也可以使用标准颜色名称字符串。如果不指定背景色,默认颜色为不可见的黑色 (0, 0, 0, 0) ,即透明背景的图像。

以下代码分别创建了一个大小为 100x200 、红色背景的 Image 图像和一个大小为 20x20 、无背景色的图像,并分别保存到文件中:

>>> im = Image.new('RGBA', (100, 200), 'red')
>>> im.save('redImage.png')
>>> im2 = Image.new('RGBA', (20, 20))
>>> im2.save('transparentImage.png')

2.3 裁剪图像、展示图像

在图像内选择一个矩形区域、并删除矩形外的一切,就是裁剪图像Image 对象的 crop() 方法接受一个矩形元组,返回一个 Image 对象表示裁剪后的图像。裁剪不会修改原图像,而是返回一个新的 Image 对象。

以下代码向 crop() 传入一个矩形元组,指定 zophie.png 中的一个矩形区域,包含这只猫的脸,裁剪 catIm 后得到一个新的 Image 对象,表示 230x215 的裁剪区域,保存在 croppedIm 中,最后调用 croppedIm.save() 存入文件 cropped.png

>>> croppedIm = catIm.crop((335, 345, 565, 560))
>>> croppedIm.save('cropped.png')

下面调用 Image 对象的 show() 方法,展示 catIm, croppedIm 这两张图像:

2.4 复制、粘贴图像到其他图像

copy() 方法返回一个和原先的 Image 对象一样的新的 Image 对象。如果需要修改图像、同时保持原有版本不变,这就很有用了。以下代码复制 catIm 对象得到了 catCopyIm ,两者是独立的 Image 对象、只是图像相同。可以随意修改 catCopyIm ,将它存入一个新的文件,而 zophie.png 不会改变:

>>> catCopyIm = catIm.copy()

Image 对象的 paste() 方法,能将另一个图像粘贴在它身上的某个位置,它的两个参数分别是源 Image 对象和一个 (x, y) 坐标元组,坐标元组指明源 Image 对象粘贴到主 Image 对象时左上角的位置。注意,paste() 方法会在原图 Image 对象上进行修改,不会返回粘贴后图像的 Image 对象。我们尝试粘贴一个较小的图像到 catCopyIm

>>> faceIm = catIm.crop((335, 345, 565, 560))
>>> faceIm.size  
(230, 215)
>>> catCopyIm.paste(faceIm, (0, 0))
>>> catCopyIm.paste(faceIm, (400, 500))
>>> catCopyIm.show()                   
>>> catCopyIm.save('pasted.png')

调用 show() 方法展示的新图像如下:

以下代码用 Zophie 的头平铺满整个图像

>>> catImWidth, catImHeight = catIm.size  
>>> faceImWidth, faceImHeight = faceIm.size
>>> catCopy2 = catIm.copy()
>>>> for left in range(0, catImWidth, faceImWidth): 
...     for top in range(0, catImHeight, faceImHeight):
...         print(left, top)
...         catCopy2.paste(faceIm, (left, top))
... 
0 0
0 215
0 430
0 645
0 860
......
690 860
690 1075
>>> catCopy2.show()
>>> catCopy2.save('tiled.png')

粘贴完成后进行展示和保存:

这一粘贴函数还有需要注意的地方,一般而言,调用 paste() 会将透明像素作为白色像素进行粘贴;如果要粘贴的图像有透明像素,要再将该 Image 对象作为第三个参数传入,这样就不会粘贴一个不透明的矩形。这就是所谓的遮罩 Image 对象,遮罩是一个 Image 对象、只有 alpha 值是有效的、红绿蓝值将被忽略,遮罩告诉 paste() 函数哪些像素应该复制、哪些应该保持透明。

2.5 调整图像大小

resize() 方法在 Image 对象上调用,返回指定宽度和高度的一个新 Image 对象,它接受二整数元组作为参数。注意,这一方法的元组参数中只允许整数,所以以下代码中要取整。

>>> quartersizedIm = catIm.resize((int(width / 2), int(height / 2)))
>>> svelteIm = catIm.resize((width, height + 300))
>>> quartersizedIm.save('quartersized.png') 
>>> svelteIm.save('svelte.png') 

2.6 旋转和翻转图像

rotate() 方法可以旋转图像,返回旋转后的一个新 Image 对象,它接受一个整数或浮点数,表示图像逆时针旋转的度数。以下代码将其简单旋转了90度、180度、270度:

>>> catIm.rotate(90).show()
>>> catIm.rotate(180).show() 
>>> catIm.rotate(270).show() 

图像依次展示如下:
在这里插入图片描述
不知道注意了没有,在 Image 对象上调用 rotate(angle)当图像本身旋转90度或270度时,宽度和高度发生变化;如果旋转其他角度如6度时,图像的原始尺寸会保持,只是有些边角会被边缘截断,并且会用黑色的背景(Windows上)/透明像素(OS X上)来填补旋转造成的缝隙。然而无论如何,保存为图片文件后,文件大小不会变化,仍然是原始文件大小。不过凡事皆有例外,expand 参数设置为 True 之后,就会放大图像的尺寸以适应非180度旋转后的新图像,从而保持图片边角可见,如果有空隙也会用黑色背景填充,保存为图片文件后文件尺寸将变大

>>> catIm.rotate(6).show()
>>> catIm.rotate(6, expand=True).show()

展示的图像如下(为适应博客展示进行了一定的缩放):

调用 transpose() 方法,将返回镜像翻转后或逆时针旋转特定角度(90度、180度、270度)的一个新 Image 图像,只是必须向 transpose() 方法传入 Image.FLIP_TOP_BOTTOMImage.FLIP_LEFT_RIGHT 或其他参数:

>>> help(catIm.transpose)
Help on method transpose in module PIL.Image:

transpose(method) method of PIL.PngImagePlugin.PngImageFile instance
    Transpose image (flip or rotate in 90 degree steps)

    :param method: One of :py:data:`PIL.Image.FLIP_LEFT_RIGHT`,
      :py:data:`PIL.Image.FLIP_TOP_BOTTOM`, :py:data:`PIL.Image.ROTATE_90`,
      :py:data:`PIL.Image.ROTATE_180`, :py:data:`PIL.Image.ROTATE_270`,
      :py:data:`PIL.Image.TRANSPOSE` or :py:data:`PIL.Image.TRANSVERSE`.
    :returns: Returns a flipped or rotated copy of this image.

建议自己一一试一下这些代码,体验一下和 rotate() 特定角度的不同——没有用黑色背景填充(指得是90度和270度旋转时),保存为图片文件后文件大小要么不变、要么宽度和高度交换

>>> catIm.transpose(Image.FLIP_LEFT_RIGHT).show()
>>> catIm.transpose(Image.FLIP_TOP_BOTTOM).show() 
>>> catIm.transpose(Image.TRANSPOSE).show()       
>>> catIm.transpose(Image.TRANSVERSE).show() 
>>> catIm.transpose(Image.ROTATE_90).show()  
>>> catIm.transpose(Image.ROTATE_180).show() 
>>> catIm.transpose(Image.ROTATE_270).show()

2.7 更改单个像素

对于单个像素的颜色,可以用 getpixel(), putpixel() 方法取得和设置,它们都接受一个坐标 (x, y) 元组(表示像素的坐标),putpixel() 方法还接受一个元组作为该像素的颜色,可以是四整数RGBA元组或三整数RGB元组:

>>> im  = Image.new('RGBA', (100, 100))
>>> im.getpixel((0, 0))
(0, 0, 0, 0)
>>> for x in range(100):
...     for y in range(50):
...         im.putpixel((x, y), (210, 210, 210))
... 
>>> from PIL import ImageColor
>>> for x in range(100):
...     for y in range(50, 100):
...         im.putpixel((x, y), ImageColor.getcolor('darkgrey', 'RGBA'))
...
>>> im.getpixel((0, 0))
(210, 210, 210, 255)
>>> im.getpixel((0, 50))
(169, 169, 169, 255)
>>> im.show()

以上代码中先得到一个 100x100 的透明正方形,对一些坐标调用 getpixel() 返回 (0, 0, 0, 0) 。之后使用嵌套循环遍历图像上半部分的所有像素,用 putpixel() 设置每个像素的颜色 (210, 210, 210) ,即浅灰色。然后,由于 putpixel() 方法不接受标准颜色名称,所以必须使用 ImageColor.getcolor() 来获得 'darkgray' 的颜色元组,再循环遍历图像的下半部分像素,设置为 'darkgray' 深灰色。如下图所示:

这样一个个绘制像素实在辛苦,如果要绘制形状,之后会使用 ImageDraw() 函数。


3. 练手:用Python添加标志

具体见这篇文章:【Python】用Pillow库为几百张二次元壁纸添加徽标


4. 在图像上绘画

如果需要在图像上进行绘画,如划线、矩形或其他简单形状,要用Pillow的 ImageDraw 。首先导入 Image, ImageDraw ,然后创建一个新的 200x200 的白色图像,将其传入 ImageDraw.Draw() 函数,得到一个 ImageDraw 对象,调用这一新对象的方法,就可以在 Image 对象上绘制形状和文字。

>>> from PIL import Image, ImageDraw
>>> im = Image.new('RGBA', (200, 200), 'white')
>>> draw = ImageDraw.Draw(im)

ImageDraw 对象的完整文档在:http://pillow. readthedocs.org/en/latest/reference/ImageDraw.html,除了这里提到的还有其他绘制方法。

4.1 绘制形状

ImageDraw 对象上有如下方法,用于绘制形状,其中的 fill, outline 两个参数是可选的,未指定时默认为白色:

  • point(xy, fill) 方法绘制单个像素,xy 表示要画的点的列表,可以是 (x, y) 坐标的元组的列表,或者是没有元组的 xy 坐标的列表,如 [(x1, y1), (x2, y2), ...][x1, y1, x2, y2, ...] 。可选的 fill 参数是点的填充颜色,要么是一个 'RGBA' 元组,要么是颜色名称的字符串。
  • 线line(xy, fill, width) 方法绘制一条线或一系列的线,xy 要么是一个元组的列表,要么是一个整数列表,如 [(x1, y1), (x2, y2), ...][x1, y1, x2, y2, ...] 。每个点都是正在绘制的线上的一个连接点。可选的 fill 参数是线的颜色,用法同上。可选的 width 参数是线的宽度,默认是 1
  • 矩形rectangle(xy, fill, outline) 方法绘制一个矩形,xy 参数是一个矩形元组,形式为 (left, top, right, bottom) 。可选的 fill 参数是矩形内部的填充颜色,用法同上。可选的 outline 参数是矩形轮廓的颜色。
  • 椭圆ellipse(xy, fill, outline) 方法绘制一个椭圆,xy 参数是一个矩形元组 (left, top, right, bottom) ,表示正好包含该椭圆的矩形。可选的 fill 参数是椭圆内的填充色。可选的 outline 参数是椭圆轮廓的颜色。
  • 多边形polygon(xy, fill, outline) 方法绘制任意的多边形,xy 参数是一个元组的列表,或是一个整数列表,如 [(x1, y1), (x2, y2), ...][x1, y1, x2, y2, ...] ,表示多边形边的连接点。最后一个坐标自动连接到第一对坐标。可选的 fill 参数是多边形内部的颜色。可选的 outline 参数是多边形轮廓的颜色。

示例代码如下:

>>> from PIL import Image, ImageDraw
>>> im = Image.new('RGBA', (200, 200), 'white')
>>> draw = ImageDraw.Draw(im)
>>> draw.line([(0, 0), (199, 0), (199, 199), (0, 199), (0, 0)], fill = 'black') 
>>> draw.rectangle((20, 30, 60, 60), fill = 'blue')
>>> draw.ellipse((120, 30, 160, 60), fill = 'red') 
>>> draw.polygon([(57, 87), (79,62), (94, 85), (120, 90), (103, 113)], fill = 'brown')
>>> for i in range(100, 200, 10):
...     draw.line([(i, 0), (200, i - 100)], fill = 'green')
...
>>> im.save('drawing.png')
>>> im.show()

先生成 200x200 的白色 Image 图像,然后传入 ImageDraw.Draw() 获得 ImageDraw 对象,保存在 draw 中。然后在图像边缘画上黑色正方形轮廓;在里面画上一个蓝色的矩形,左上角是 (20, 30) 、右下角是 (60, 60) ;还有一个红色的椭圆,由 (120, 30), (160, 60) 的矩形来定义;一个棕色的多边形,五个顶点;还有一些绿线,用 for 循环绘制:

4.2 绘制文本

ImageDraw 对象的 text() 方法可以在图像上绘制文本,四个参数如下:

  • xy :两个整数的元组,指定文本区域的左上角;
  • text :想写入的文本字符串;
  • fill :可选参数,文本的颜色;
  • font :可选参数,是一个 ImageFont 对象,用于设置文本的字体和大小

由于一块文本在给定字体下的大小难以预先知道,所以 ImageDraw 提供了 textsize() 方法,第一个参数是 text 文本字符串,第二个参数是可选的 ImageFont 对象,它返回一个二整数元组,表示以指定的字体写入图像后文本 text 的宽度和高度。利用其返回值,就可以精确计算文本放在图像上的位置。

关键是 ImageFont 对象,要创建这一对象,需要 from PIL import ImageFont ,导入后就可以调用 ImageFont.truetype() 函数:

  • 参数一是字符串,表示字体的 TrueType 文件(具有 .TTF 文件扩展名,能在Windows的 C:\Windows\Fonts 中找到),即硬盘上实际的字体文件。实际上代码中并不需要输入这一路径作为字符串的一部分(非要输入也没辙),因为Python会自动在这一目录中搜索字体,找不到就报错。
    以下就是字体库。需要注意的是,由于一种字体对应多种字体文件,比如粗体、斜体等等,实际的字体文件英文名称需要选中相应字体、右击属性项才能找到。如果只使用表面的字体名,就等着爆出 OSError: cannot open resource 错误吧。

  • 参数二是一个整数,表示字体大小的点数(非像素!)。Pillow创建的PNG图像默认是每英寸72像素一点是 1/72 英寸

输入以下代码:

>>> from PIL import Image, ImageDraw, ImageFont
>>> import os
>>> im = Image.new('RGBA', (200, 200), 'white')
>>> draw = ImageDraw.Draw(im)
>>> draw.text((20, 150), 'Hello', fill='blue')
>>> fontsFolder = 'C:\\Windows\\Fonts'
>>> arialFont = ImageFont.truetype(os.path.join(fontsFolder, 'arial.ttf'), 32) # 使用完整路径
>>> draw.text((100, 150), 'Howdy', fill='grey', font=arialFont) # 绘制'Howdy',采用灰色、32点Arial字体
>>> im.show()
>>> arialFont = ImageFont.truetype('arial.ttf', 32) # 只使用字体文件英文名称
>>> draw.text((50, 50), 'Hey!', fill='chocolate', font=arialFont)
>>> im.show()
>>> consolaFont = ImageFont.truetype('consolaz.ttf', 32)
>>> draw.text((70, 120), 'Sunshine', fill='red', font=consolaFont)
>>> im.show()

最后展示的图片如下所示:


5. 实践项目

有几句槽不吐不快,我看的是书的中文版,可能是没有勘误过的缘故?光这一章就有好几个错字,习题的题目和答案都有错的,也是搞无语了,就该直接看英文原版的。

5.1 扩展和修正本章项目的程序

5.2 在硬盘上识别照片文件夹

5.3 定制的座位卡

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值