JPG 还是 PNG 和内存结构有关系吗?还是只是保存到硬盘的时候,才有区别?

我最近在使用 selenium 做一个网页自动截屏的服务, 保存的截图需要保存为 jpg 文件

有两个相关的 api

selenium/webdriver/remote/webdriver.py

    def get_screenshot_as_png(self) -> bytes:
        """
        Gets the screenshot of the current window as a binary data.

        :Usage:
            ::

                driver.get_screenshot_as_png()
        """
        return b64decode(self.get_screenshot_as_base64().encode('ascii'))

    def get_screenshot_as_base64(self) -> str:
        """
        Gets the screenshot of the current window as a base64 encoded string
           which is useful in embedded images in HTML.

        :Usage:
            ::

                driver.get_screenshot_as_base64()
        """
        return self.execute(Command.SCREENSHOT)['value']

一个是 get_screenshot_as_png,另一个是 get_screenshot_as_base64

get_screenshot_as_png 中也是调用了 get_screenshot_as_base64

我就有问题了,这里的 get_screenshot_as_png 就是对 get_screenshot_as_base64 的结果做了简单的 decodeencode

这样就得到 PNG 了?那我怎么的到 JPG 呢?

难道 get_screenshot_as_base64 返回的 base64 其实就是 PNG 编解码后的 base64?

我本来想的是,图片都是三维矩阵,只有在保存到硬盘的时候,才分 pngjpg这些,是这样的吗?

还是说因为 PNG 是无损的,所以简单的编解码就好了,JPG 要有损压缩,才需要后续特殊处理?

阅读 2.9k
4 个回答

自己解决了

我的需求:我会获得 png 的 stream (type 为 bytes),我要把 png 上传到 oss,但是 png 太大了,就像转成 jpg 再上传到 oss 中

但是,简中互联网上的教程都是,教你怎么把『内存中的 png』 变成 『硬盘上的 jpg』

这是非常愚蠢的!

我需要的是:『内存中的 png』 变成 『内存中的 jpg』

简单的来讲,就是我希望,使用 python 把 png 转成 jpg 并压缩这个过程全程发生在 RAM 中,而不涉及任何硬盘读写

所以,我写了下面的代码:

import unittest
from loguru import logger
from mark import BASE_DIR
from pathlib import Path
from PIL import Image
from PIL.Image import Image as PIL_Image
import sys
import io


def png_stream_2_png_image(png_stream: bytes) -> PIL_Image:
    file_like_obj = io.BytesIO(png_stream)

    png_image = Image.open(file_like_obj)
    return png_image


def png_image_2_jpg_stream(png_image: PIL_Image, quality=100) -> bytes:
    assert png_image.format == 'PNG'
    file_like_obj = io.BytesIO()
    png_image = png_image.convert('RGB')  # PNG 是 RGBA,多了一个透明通道,JPG不支持透明通道
    png_image.save(file_like_obj, format='JPEG', quality=quality)
    stream = file_like_obj.getvalue()
    return stream


def png_stream_2_jpg_stream(png_stream: bytes, quality=100) -> bytes:
    """
    png_stream into png_imgae
    png_imgae into jpg_stream
    """
    png_image = png_stream_2_png_image(png_stream)
    jpg_stream = png_image_2_jpg_stream(png_image, quality)
    return jpg_stream


def png_image_2_png_stream(png_image: PIL_Image) -> bytes:
    assert png_image.format == 'PNG'
    file_like_obj = io.BytesIO()
    png_image.save(file_like_obj, format='PNG')
    stream = file_like_obj.getvalue()
    return stream


class TestCompress(unittest.TestCase):
    def test_compress_on_memory_2(self):
        """
        python -m unittest testing.test_compress.TestCompress.test_compress_on_memory_2
        """

        src_file_path = BASE_DIR/'static/img/94dcb906ff774e7ab4d6e8b1abcc147b.png'

        png_image = Image.open(src_file_path)

        png_stream = png_image_2_png_stream(png_image)
        jpg_stream = png_image_2_jpg_stream(png_image, quality=50)

        # logger.debug(png_stream)
        logger.debug(type(png_stream))
        logger.debug(len(png_stream))

        logger.debug(type(jpg_stream))
        logger.debug(len(jpg_stream))

        logger.debug(f'压缩比: {round(len(jpg_stream)/len(png_stream)*100)}%')

输出结果如下:

─➤  python -m unittest testing.test_compress.TestCompress.test_compress_on_memory_2
2022-07-15 13:05:31.375 | DEBUG    | testing.test_compress:test_compress_on_memory_2:59 - <class 'bytes'>
2022-07-15 13:05:31.375 | DEBUG    | testing.test_compress:test_compress_on_memory_2:60 - 1075380
2022-07-15 13:05:31.375 | DEBUG    | testing.test_compress:test_compress_on_memory_2:62 - <class 'bytes'>
2022-07-15 13:05:31.375 | DEBUG    | testing.test_compress:test_compress_on_memory_2:63 - 172836
2022-07-15 13:05:31.375 | DEBUG    | testing.test_compress:test_compress_on_memory_2:65 - 压缩比: 16%

JPG 和 PNG 是两种编码,需要 JPG 的话自己转换一下。

from PIL import Image

im = Image.open("in.png")
rgb_im = im.convert('RGB')
rgb_im.save('out.jpg')

比如1920×1080的图片,在内存里一般都是一个1920×1080×3的字节数组,也可能是1920×1080×4(包含透明通道的话)。
3代表的是rgb三通道,每个像素由rgb三通道决定,它们取值在0-255之间。 而且一般不用三维矩阵,比如opencv中就使用一维数组去保存图片,类似第一个像素点是a[0] a[1] a[2], 第二个像素点是a[3] a[4] a[5]。

当需要保存到磁盘时,为了解决存储空间,才会以png/jpg等格式编码压缩大小。

b64decode(self.get_screenshot_as_base64().encode('ascii'))

上边这一行,返回值是字节数组,应该和png/jpg格式无关才对。你可以看一下返回的字节数组的长度,如果能被1920 * 1080(屏幕分辨率)整除,应该就是和png/jpg格式无关。

图片要显示,在内存中为了方便直接显示,一般都是解码开的图形数据啦,这可以认为无压缩的原始数据,只有在保存为图片格式时才进行编码保存。

你想要保存为jpg,可以png转jpg格式,这个有第三方库支持的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏