精华内容
下载资源
问答
  • 关于图片的Exif信息

    千次阅读 2019-08-21 23:30:11
    听到小伙伴说有关审核图片的接口经常超时的时候,正好看了下这块代码。主要是从cdn取几张图片,校验创建时间。 这块逻辑是用下载图片读流的方式处理。受制于网络及图片文件,自然性能快不了。 二 优化 本文不...

    一 问题

    听到小伙伴说有关审核图片的接口经常超时的时候,正好看了下这块代码。主要是从cdn取几张图片,校验创建时间。

    这块逻辑是用下载图片读流的方式处理。受制于网络及图片文件,自然性能快不了。

    二 优化

      本文不展开图片的反作弊,只简单介绍下图片的创建时间识别。

    我们只是为了获取图片的拍摄时间做初步判断,而不是图片内容本身。所以有没有办法只获取图片信息而不去下载图片就好了。

    答案是有的。

    先说下exif.  EXIF(Exchangeable Image File)是 “可交换图像文件” 的缩写,当中包含了专门为数码相机的照片而定制的元数据,可以记录数码照片的拍摄参数、缩略图及其他属性信息,简单来说,Exif 信息是镶嵌在 JPEG/TIFF 图像文件格式内的一组拍摄参数.正常的手机拍摄的图片会包含这些信息。

    看个样例:

    {
        "ApertureValue": {"value": "193685/85136"},
        "BrightnessValue": {"value": "100718/10007"},
        "ColorSpace": {"value": "1"},
        "DateTime": {"value": "2019:08:16 15:54:15"},
        "DateTimeDigitized": {"value": "2019:08:16 15:54:15"},
        "DateTimeOriginal": {"value": "2019:08:16 15:54:15"},
        "ExifTag": {"value": "192"},
        "ExposureBiasValue": {"value": "0/1"},
        "ExposureMode": {"value": "0"},
        "ExposureProgram": {"value": "2"},
        "ExposureTime": {"value": "1/1721"},
        "FNumber": {"value": "11/5"},
        "FileSize": {"value": "241531"},
        "Flash": {"value": "24"},
        "FocalLength": {"value": "83/20"},
        "FocalLengthIn35mmFilm": {"value": "29"},
        "Format": {"value": "jpg"},
        "GPSLatitude": {"value": "31deg 55' 3.870\" "},
        "GPSLongitude": {"value": "117deg 18' 4.110\" "},
        "GPSTag": {"value": "699"},
        "ISOSpeedRatings": {"value": "32"},
        "ImageHeight": {"value": "1001"},
        "ImageWidth": {"value": "750"},
        "LensMake": {"value": "Apple"},
        "LensModel": {"value": "iPhone 6 back camera 4.15mm f/2.2"},
        "LensSpecification": {"value": "83/20 83/20 11/5 11/5"},
        "Make": {"value": "Apple"},
        "MeteringMode": {"value": "5"},
        "Model": {"value": "iPhone 6"},
        "Orientation": {"value": "1"},
        "PixelXDimension": {"value": "750"},
        "PixelYDimension": {"value": "1001"},
        "ResolutionUnit": {"value": "2"},
        "SceneType": {"value": "1"},
        "SensingMethod": {"value": "2"},
        "ShutterSpeedValue": {"value": "100881/9385"},
        "Software": {"value": "12.3"},
        "SubSecTimeDigitized": {"value": "039"},
        "SubSecTimeOriginal": {"value": "039"},
        "SubjectArea": {"value": "1631 1223 1795 1077"},
        "WhiteBalance": {"value": "0"},
        "XResolution": {"value": "144/1"},
        "YResolution": {"value": "144/1"}}

    参数很多,很有用的(详细可以看下面的)。

    这里我们关注时间:DateTimeOriginal 这是原始时间,不能被修改的。还有个是DateTime。图像最后一次被修改时的日期/时间. 

    判断这个可以做初步的校验,其他的图片相似度反作弊还是有专业的做吧。

    关于接口优化,因为图片已经在cdn上,每家不一样获取exif不一样。

    阿里云是:http://image-demo.img.aliyuncs.com/f.jpg@exif

    七牛云:http://7xt44n.com2.z0.glb.qiniucdn.com/exif.png?exif

    并非每一张图片都包含 exif 信息,需要自己结合业务判断。

    另外,有的根据Orientation 去调整图片的角度显示正常的,还有根据gps信息去判断位置的。这个比较敏感,上面的gps数据是假的。

    这篇文章整理的很好。http://blog.sina.com.cn/s/blog_651251e60102uz3d.html

    JPEG格式和标记

    每一个JPEG文件的内容都开始于一个二进制的值 '0xFFD8', 并结束与二进制值'0xFFD9'. 在JPEG的数据 中有好几种类似于二进制 0xFFXX 的数据, 它们都统称作 "标记", 并且它们代表了一段JPEG的 信息数据. 0xFFD8 的意思是 SOI图像起始(Start of image), 0xFFD9 则表示 EOI图像结束 (End of image). 这两个特殊的标记的后面都不跟随数据, 而其他的标记在后面则会附带数据. 标记的基本 格式如下.

    0xFF+标记号(1个字节)+数据大小描述符(2个字节)+数据内容(n个字节)

    数据大小描述符(2个字节) 是 "Motorola" 的字节顺序, 数据的低位被存放在高地址,也就是 BigEndian. 请注意上面中的 "数据内容" 中包含他前面的数据大小描述符, 如果下面的是一个标记的话;

    FF C1 00 0C

    它就表示这个标记(0xFFC1) 的数据占 0x000C(等于12)个字节. 但是这个数据大小'12' 包含了 "数据大小" 描述符, 也就是在0x000C后面它只允许带有10 个字节大小的数据.

    在JPEG 格式中, 最开始先是用一些标记来描述数据, 然后是放置 SOS数据流的起始(Start of stream) 标记. 在SOS标记的后面才是, 存放JPEG图像的数据流并终结于EOI标记.
     

    SOI 标记 标记 XX 的大小=SSSS 标记 YY 的大小=TTTT SOS 标记 的大小=UUUU 图像数据流 EOI 标记
    FFD8 FFXX SSSS DDDD...... FFYY TTTT DDDD...... FFDA UUUU DDDD.... I I I I.... FFD9

    Exif使用的标记

    0xFFE0~0xFFEF之间的标记被叫做 "应用标记", 它们在JPEG图像解码中不是必须存在的. 它们被使用于用户的应用程序之中. 例如, 老款的olympus/canon/casio/agfa 数字相机使用 JFIF(JPEG文件交换格式/JPEG File Interchange Format)来存储图像. JFIF 使用 APP0(0xFFE0) 标记来插入数字相机的配置信息数据和缩略图.

    Exif也使用应用标记来插入数据, 但是Exif 使用 APP1(0xFFE1)标记来避免与JFIF格式的 冲突. 且每一个 Exif 文件格式都开始于它, 如;
     

    SOI 标记 APP1 标记 APP1 数据 Other 标记
    FFD8 FFE1 SSSS 457869660000 TTTT...... FFXX SSSS DDDD......


    该图像文件从SOI(0xFFD8) 标记开始, 因此它是一个 JPEG 文件. 后面马上跟着 APP1 标记. 而它的所有 Exif数据都被存储在 APP1 数据域中. 上面的 "SSSS" 这部分表示 APP1 数据域 (Exif data area)的大小. 请注意这里的大小 "SSSS" 包含描述符本身的大小.

    在 "SSSS"后面, 是 APP1 的数据. 其中第一个部分是一个特殊的数据,它用来标识是否是 Exif, 其值是ASCII 字符 "Exif" 和 两个0x00字节 的组合字符串.

    在 APP1 标记域的后面是, 跟随着其他的 JPEG 标记.

    IFD0 (主图像)使用的标签

    标签号 标签名 格式 组件数 描述
    0x010e ImageDescription ascii string   用来描述图像. 双字节的字符码不能使用, 如 中文/韩文/日文.
    0x010f Make ascii string   表示数字相机的制造商. 在 Exif 标准中, 这个标签是可选的, 但是在DCF中它是必需的.
    0x0110 Model ascii string   表示数字相机的模块代码. 在 Exif 标准中, 这个标签是可选的, 但在DCF中它也是必需的.
    0x0112 Orientation unsigned short 1
    Value 0th Row 0th Column
    1 top left side
    2 top right side
    3 bottom right side
    4 bottom left side
    5 left side top
    6 right side top
    7 right side bottom
    8 left side bottom
    当拍照时, 相机相对于场景的方向. 在右边表示的是'0th row' 以及 '0th column' 在视觉位置上的关系.
    0x011a XResolution unsigned rational 1 图像的 显示/打印 分辨率. 缺省值是 1/72英寸, 但是它没有意义因为个人PC在 显示/打印 图像的时候不使用这个值.
    0x011b YResolution unsigned rational 1
    0x0128 ResolutionUnit unsigned short 1 XResolution(0x011a)/YResolution(0x011b)的单位. '1' 表示没有单位, '2' 意味着英寸, '3' 表示厘米. 缺省值是 '2'(英寸).
    0x0131 Software ascii string   显示固件的版本号(数字相机的内部控制软件).
    0x0132 DateTime ascii string 20 图像最后一次被修改时的日期/时间. 日期的格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共 20个字节. 如果没有设置时钟或者数字相机没有时钟, 则这个域是用空格来填充. 通常, 它和DateTimeOriginal(0x9003)具有相同的值
    0x013e WhitePoint unsigned rational 2 定义图像白点(white point/白点:在彩色分色、照相或摄影时作为色彩平衡测量用途的参考点) 的色度(chromaticity). 如果图像是用CIE标准照度 D65(著名的是 '光线/daylight'的国际标准), 这个值是 '3127/10000,3290/10000'.
    0x013f PrimaryChromaticities unsigned rational 6 定义图像的原始色度. 如果图像使用 CCIR 推荐 709原始色度, 则这个值是 '640/1000,330/1000,300/1000,600/1000,150/1000,0/1000'.
    0x0211 YCbCrCoefficients unsigned rational 3 当图像的格式是 YCbCr(JPEG的格式), 这个值表示转换成 RGB格式的一个常量. 通常, 这个值是'0.299/0.587/0.114'.
    0x0213 YCbCrPositioning unsigned short 1 当图像的格式是 YCbCr 并且使用 '子采样/Subsampling'(色度数据的剪切值, 所有的数字相机都使用), 定义了subsampling 像素阵列的色度采样点. '1'表示像素阵列的中心, '2' 表示基准点.
    0x0214 ReferenceBlackWhite unsigned rational 6 表示黑点(black point)/白点 的参考值. 在YCbCr 格式中,前两个值是 Y的黑点/白点, 下两个值是 Cb, 最后两个值是 Cr. 而在 RGB 格式中, 前两个表示R的黑点/白点, 下两个是 G, 最后两个是 B.
    0x8298 Copyright ascii string   表示版权信息
    0x8769 ExifOffset unsigned long 1 Exif 子IFD的偏移量


    Exif 子IFD使用的标签

    标签号 标签名 格式 组件数 描述
    0x829a ExposureTime unsigned rational 1 曝光时间 (快门速度的倒数). 单位是秒.
    0x829d FNumber unsigned rational 1 拍照时的光圈F-number(F-stop).
    0x8822 ExposureProgram unsigned short 1 拍照时相机使用的曝光程序. '1' 表示手动曝光, '2' 表示正常程序曝光, '3' 表示光圈优先曝光, '4' 表示快门优先曝光, '5' 表示创意程序(慢速程序), '6' 表示动作程序(高速程序), '7'表示 肖像模式, '8' 表示风景模式.
    0x8827 ISOSpeedRatings unsigned short 2 CCD 的感光度, 等效于 Ag-Hr 胶片的速率.
    0x9000 ExifVersion undefined 4 Exif 的版本号. 用4个ASCII字符来存储. 如果图片是基于Exif V2.1的, 这个值是 "0210". 因为它不是一个用NULL(0x00)来终结的字符串,所以这里的类型是 'undefined'.
    0x9003 DateTimeOriginal ascii string 20 照片在被拍下来的日期/时间. 使用用户的软件是不能被修改这个值的. 日期的格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共占用20个字节. 如果数字相机没有设置时钟或者 数字相机没有时钟, 这个域使用空格来填充. 在Exif标准中, 这个标签是可选的, 但是在 DCF中是必需的.
    0x9004 DateTimeDigitized ascii string 20 照片被数字化时的日期/时间. 通常, 它与DateTimeOriginal(0x9003)具有相同的值. 数据格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共占用20个字节. 如果数字相机没有设置时钟或者 数字相机没有时钟, 这个域使用空格来填充. 在Exif标准中, 这个标签是可选的, 但是在 DCF中是必需的.
    0x9101 ComponentsConfiguration undefined   表示的是像素数据的顺序. 大多数情况下RGB格式使用 '0x04,0x05,0x06,0x00' 而YCbCr 格式使用 '0x01,0x02,0x03,0x00'. 0x00:并不存在, 其他的对应关系为 0x01:Y, 0x02:Cb, 0x03:Cr, 0x04:Red, 0x05:Green, 0x06:Bllue.
    0x9102 CompressedBitsPerPixel unsigned rational 1 JPEG (粗略的估计)的平均压缩率.
    0x9201 ShutterSpeedValue signed rational 1 用APEX表示出的快门速度. 为了转换成原始的 'Shutter Speed'; 则先要计算2的ShutterSpeedValue次幂, 然后求倒数. 例如, 如果 ShutterSpeedValue 是 '4', 快门速度则是1/(24)=1/16秒.
    0x9202 ApertureValue unsigned rational 1 拍照时镜头的光圈. 单位是 APEX. 为了转换成普通的 F-number(F-stop), 则要先计算出根号2 2 (=1.4142)的ApertureValue次幂. 例如, 如果ApertureValue 是 '5', F-number 就等于1.41425 = F5.6.
    0x9203 BrightnessValue signed rational 1 被拍摄对象的明度, 单位是 APEX. 为了从BrigtnessValue(Bv)计算出曝光量(Ev), 你必须加上 SensitivityValue(Sv).
    Ev=Bv+Sv   Sv=log2(ISOSpeedRating/3.125)
    ISO100:Sv=5, ISO200:Sv=6, ISO400:Sv=7, ISO125:Sv=5.32.
    0x9204 ExposureBiasValue signed rational 1 照片拍摄时的曝光补偿. 单位是APEX(EV).
    0x9205 MaxApertureValue unsigned rational 1 镜头的最大光圈值. 你可以通过计算根号2的MaxApertureValue次幂来转换成普通的光圈 F-number (跟ApertureValue:0x9202的处理过程一样).
    0x9206 SubjectDistance signed rational 1 到焦点的距离, 单位是米.
    0x9207 MeteringMode unsigned short 1 曝光的测光方法. '0' 表示未知, '1' 为平均测光, '2' 为中央重点测光, '3' 是点测光, '4' 是多点测光, '5' 是多区域测光, '6' 部分测光, '255' 则是其他.
    0x9208 LightSource unsigned short 1 光源, 实际上是表示白平衡设置. '0' 意味着未知, '1'是日光, '2'是荧光灯, '3' 白炽灯(钨丝), '10' 闪光灯, '17' 标准光A, '18' 标准光B, '19' 标准光C, '20' D55, '21' D65, '22' D75, '255' 为其他.
    0x9209 Flash unsigned short 1 '0' 表示闪光灯没有闪光, '1' 表示闪光灯闪光, '5' 表示闪光但没有检测反射光, '7' 表示闪光且检测了反射光.
    0x920a FocalLength unsigned rational 1 拍摄照片时的镜头的焦距长度. 单位是毫米.
    0x927c MakerNote undefined   制造商的内部数据. 一些制造商如 Olympus/Nikon/Sanyo 等在这个区域中使用IFD 格式的数据.
    0x9286 UserComment undefined   存储用户的注释. 这个标签允许使用两字节的德字符或者 unicode. 前8 个字节描述的是字符集. 'JIS' 是日文 (著名的有 Kanji).
    '0x41,0x53,0x43,0x49,0x49,0x00,0x00,0x00':ASCII
    '0x4a,0x49,0x53,0x00,0x00,0x00,0x00,0x00':JIS
    '0x55,0x4e,0x49,0x43,0x4f,0x44,0x45,0x00':Unicode
    '0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00':Undefined
    0x9290 SubsecTime ascii string   一些数字相机每秒能拍摄 2~30 张照片, 但是DateTime/DateTimeOriginal/DateTimeDigitized 标签只能记录到秒单位的时间. SubsecTime 标签就是用来记录秒后面的数据(微秒).
    例如, DateTimeOriginal = "1996:09:01 09:15:30", SubSecTimeOriginal = "130", 合并起来的原始的拍摄 时间就是 "1996:09:01 09:15:30.130"
    0x9291 SubsecTimeOriginal ascii string  
    0x9292 SubsecTimeDigitized ascii string  
    0xa000 FlashPixVersion undefined 4 存储FlashPix 的版本信息. 如果图像数据是基于 FlashPix formar Ver.1.0, 则这个值为 "0100". 因为它不是一个用NULL(0x00)来终结的字符串,所以这里的类型是 'undefined'.
    0xa001 ColorSpace unsigned short 1 定义色彩空间. DCF 图像必须使用 sRGB 色彩空间因此这个值总是 '1'. 如果这个照片使用了 其他的色彩空间, 这个值是 '65535':未校准(Uncalibrated).
    0xa002 ExifImageWidth unsigned short/long 1 主图像的尺寸大小.
    0xa003 ExifImageHeight unsigned short/long 1
    0xa004 RelatedSoundFile ascii string   如果数字相机能够纪录图像的音频数据, 则表示音频数据的名字.
    0xa005 ExifInteroperabilityOffset unsigned long 1 表示这是一个扩展"ExifR98", 细节未知. 这个值经常是IFD格式的数据. 当前这儿有两个 目录项, 第一个是 Tag0x0001, 值是"R98", 下一个是 Tag0x0002, 它的值为 "0100".
    0xa20e FocalPlaneXResolution unsigned rational 1 表示CCD的像素密度. 如果你的相机是百万像素的并且是用低分辨率(如VGA模式) 来拍摄照片, 这个值可以通过照片的分辨率来重新采样. 在这种情况下, FocalPlaneResolution 就不是CCD的实际的分辨率.
    0xa20f FocalPlaneYResolution unsigned rational 1
    0xa210 FocalPlaneResolutionUnit unsigned short 1 FocalPlaneXResoluton/FocalPlaneYResolution的单位. '1' 表示没有单位, '2'是英寸inch, '3' 表示厘米.

    注 意:一些Fujifilm的数码相机(如.FX2700,FX2900,Finepix4700Z/40i 等) 使用的值是 '3' 所以它的单位一定是 '厘米' , 但是它们的分辨率单位就变成'8.3mm?'(1/3in.?). 这是Fuji 的 BUG? 从Finepix4900Z 开始这个值就使用 '2' 了但仍然跟实际的值不吻合.
    0xa215 ExposureIndex unsigned rational 1 跟ISOSpeedRatings(0x8827)一样但是数据类型是 unsigned rational. 只有Kodak的数字相机使用 这个标签来替代 ISOSpeedRating, 我不知道这是为什么(历史原因?).
    0xa217 SensingMethod unsigned short 1 表示图像传感器单元的类型. '2' 意味着这是一个芯片颜色区域传感器, 几乎所有的数字相机都 使用这个类型.
    0xa300 FileSource undefined 1 显示图像来源. 值 '0x03' 表示图像源是数字定格相机.
    0xa301 SceneType undefined 1 表示拍摄场景的类型. 值 '0x01' 表示图像是通过相机直接拍摄出来的.
    0xa302 CFAPattern undefined   表示色彩过滤阵列(CFA) 几何模式.
    长度 类型 意义
    2 short Horizontal repeat pixel unit = n
    2 short Vertical repeat pixel unit = m
    1 byte CFA value[0,0]

    :

    :

    :

    1 byte CFA value[n-1,0]
    1 byte CFA value[0,1]

    :

    :

    :

    1 byte CFA value[n-1,m-1]

    色彩过滤和CFA值之间的关系.
    Filter Color Red Green Blue Cyan Magenta Yellow White
    CFA value 0 1 2 3 4 5 6
     
    R G
    G B
    例如, 普通的 RGB 过滤器使用左表的副本, 这个值是 '0x0002,0x0002,0x00,0x01,0x01,0x02'.

     

    展开全文
  • png、jpg图片格式的区别及

    万次阅读 2019-04-15 11:17:08
    为什么想整理这方面的类容,我觉得就像油画要了解他的颜料和画布、雕塑要了解他的石材一样,作为网页设计师也应该对图片格式的特性有一定了解,这样才能更好的表达你的创意和想法。 除此之外,我们在平时工作中...

    为什么想整理这方面的类容,我觉得就像油画家要了解他的颜料和画布、雕塑家要了解他的石材一样,作为网页设计师也应该对图片格式的特性有一定了解,这样才能更好的表达你的创意和想法。

    除此之外,我们在平时工作中也会遇到许多与图片格式相关的问题。比如设计师会奇怪为什么有些页面的产出物总是没法达到设计稿那样的品质和效果,什么样的设 计才更适合Web页面;页面重构师和前端工程师则想知道在切图的时候应采用什么图片格式、如何进行参数设置才能达到品质和性能的最优化。

    有时候我们可能会因为一张格式不正确的图片而导致设计品质的下降以及页面性能的降低。了解图片格式的特性就是为解答这些困惑从而让我们设计的产品更好、更快。

    本文主要包括以下几方面内容:

    1. 1、基本概念
    2. 2、实际应用
    3. 3、思考与实践
    4. 4、附录-Photoshop中各种参数的含义及设置技巧

    1、基本概念

    要了解图片格式的特性,首先得从一些基本概念开始。这部分内容读起来可能会比较枯燥,但如果你耐着性子读完它,相信会获益匪浅。

     

    矢量图与位图

    矢量图-完美的几何图形

    矢量图是 通过组成图形的一些基本元素,如点、线、面,边框,填充色等信息通过计算的方式来显示图形的。就好比我们在几何学里面描述一个圆可以通过它的圆心位置和半 径来描述,当然还可以通过边框的粗细、颜色以及填充的颜色等数据去描述它的样式。而电脑在显示的时候则通过这些数据去绘制出我们定义的图像。

    矢量图的优点在于文件相对较小,并且放大缩小不会失真。缺点则是这些完美的几何图形很难表现自然度高的写实图像。

    需要强调说明的是我们在web页面上所使用的图像都是位图,即便有些称为矢量图形(如矢量icon等)也是指通过矢量工具进行绘制然后再转成位图格式在web上使用的(区别于像素绘制的图形)。

    位图-神奇的拼图

    位图又叫 像素图或栅格图,它是通过记录图像中每一个点的颜色、深度、透明度等信息来存储和显示图像。一张位图就好比一幅大的拼图,只不过每个拼块都是一个纯色的像 素点,当我们把这些不同颜色的像素点按照一定规律排列在一起的时候,就形成了我们所看到的图像。所以当我们放大一幅像素图时,能看到这些拼片一样的像素点 (如下图)。

    位图的优点是利于显示色彩层次丰富的写实图像。缺点则是文件大小较大,放大和缩小图像会失真。

    位图示意

    尽管我们在web页面中所使用的JPG、PNG、GIF格式的图像都是位图,即他们都是通过记录像素点的数据来保存和显示图像,但这些不同格式的图像在记录这些数据时的方式却不一样,这就是涉及到有损压缩无损压缩的区别。

    有损压缩与无损压缩

    有损压缩-你看到的不一定是真实的

    按照我的理解有损压缩就 是在存储图像的时候并不完全真实的记录图像上每个像素点的数据信息,它会根据人眼观察现实世界的特性(人眼对光线的敏感度比对颜色的敏感度要高,生物实验 证明当颜色缺失时人脑会利用与附近最接近的颜色来自动填补缺失的颜色)对图像数据进行处理,去掉那些图像上会被人眼忽略的细节,然后使用附近的颜色通过渐 变或其他形式进行填充。这样既能大大降低图像信息的数据量,又不会影响图像的还原效果。

    有损压缩示意图

    JPG是我们最常见的采用有损压缩对图像信息进行处理的图片格式。JPG在存储图像时会把图像分解成8*8像素的栅格(如上图),然后对每个栅格的数据进 行压缩处理,当我们放大一幅图像的时候,就会发现这些8*8像素栅格中很多细节信息被去除,而通过一些特殊算法用附近的颜色进行填充(为了让大家看得更清 楚我将图像的压缩比率调到很低)。这也是为什么我们用JPG存储图像有时会产生块状模糊的原因。

    无损压缩-最精确的拼图

    相对有损压缩而言无损压缩则 会真实的记录图像上每个像素点的数据信息,但为了压缩图像文件的大小会采取一些特殊的算法。无损压缩的压缩原理是先判断图像上哪些区域的颜色是相同的,哪 些是不同的,然后把这些相同的数据信息进行压缩记录,(例如一片蓝色的天空之需要记录起点和终点的位置就可以了),而把不同的数据另外保存(例如天空上的 白云和渐变等数据)。

    无损压缩示意图

    PNG是我们最常见的一种采用无损压缩的图片格式。无损压缩在存储图像前会先判断图像上哪些地方是相同的哪些地方是不同的,为此需要对图像上所有出现的颜色进行索引(如上图),我们把称这些颜色称为索引色。索引色就好比绘制这幅图像的“调色版”,PNG在显示图像的时候则会用“调色版”上的这些颜色去填充相应的位置。

    这里大家可能会疑惑既然PNG采用的是无损压缩为什么我们保存的PNG格式图片还会有失真呢?这是因为无损压缩只是说它的压缩方式会尽可能真实的还原图 像,但从它的压缩原理我们可以知道它是通过索引图像上相同区域的颜色进行压缩和还原的,这就意味着只有在图像上出现的颜色数量小于我们可以保存的颜色数量 时,我们才能真实的记录和还原图像,否则就会丢失一些图像信息(PNG8最多只能索引256种颜色,所以对于颜色较多的图像不能真实还原;PNG24则可 以保存1600多万种颜色,基本能够真实还原我们人类肉眼所可以分别的所有颜色;PNG格式最多可以保存48位颜色通道)。而对于有损压缩来说,不管图像 上的颜色多少,都会损失图像信息。

    JPG和PNG

    关于JPG和PNG的基本信息介绍这里就不赘述了,有兴趣详细了解的同学可以去这里:
    什么是JPG什么是PNG。另外这里我们也不对GIF进行讨论,是因为PNG就是为取代GIF而生的,而且PNG的压缩算法也要优于GIF,所以只要不是需要动画效果的地方强烈建议都采用PNG格式图片。

    这里我们不妨把JPG和PNG的一些特性进行一个简单对比:

    格式 压缩模式 交错支持 透明支持 动画支持
    JPG 有损压缩 支持 不支持 不支持
    PNG 无损压缩 支持 支持 不支持

    JPG的特性

    1. 1、支持摄影图像或写实图像的高级压缩,并且可利用压缩比例控制图像文件大小。
    2. 2、有损压缩会使图像数据质量下降,并且在编辑和重新保存JPG格式图像时,这种下降损失会累积。
    3. 3、JPG不适用于所含颜色很少、具有大块颜色相近的区域或亮度差异十分明显的较简单的图片。

    PNG的特性

    1. 1、能在保证最不失真的情况下尽可能压缩图像文件的大小。
    2. 2、PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。
    3. 3、对于需要高保真的较复杂的图像,PNG虽然能无损压缩,但图片文件较大,不适合应用在Web页面上。

    PNG8与PNG24

    提到PNG格式就不得不提到PNG8和PNG24,这种叫法并非官方定义,不过由于普遍使用已经被大家广泛接受了。通过前面的介绍我们知道PNG采用无损压缩是通过索引色去存储和还原图像的,PNG8和PNG24后面的数字则是代表这种PNG格式最多可以索引和存储的颜色值。”8″代表2的8次方也就是256色,而24则代表2的24次方大概有1600多万色。

    不仅如此,PNG8还支持1位的布尔透明通道,所谓布尔透明指的是要么完全透明要么完全不透明。而PNG24则支持8位(256阶)的alpha通道透明,也就是说可以存储从完全透明到完全不透明一共256个层级的透明度(即所谓的半透明)。

    格式 最高支持色彩通道 索引色编辑支持 透明支持
    PNG8 256色 支持 支持布尔透明
    PNG24 约1600万色 不支持 支持8位(256阶)alpha透明

    可能通过以上特性的对比大家还不能很直观的理解在实际应用中到底因该选择什么格式的图片文件,我们不妨结合上面的基本概念通过几个例子去给大家说明。

    2、实际应用

    什么时候应该使用PNG

    示例1

    下图是淘宝网最常见的一个图片即“立刻购买”按钮,这里我尝试用JPG和PNG8格式分别进行保存,可以看到保存的结果有两个值得注意的地方:

    1. 1、JPG保存的文件大小是PNG保存的文件大小的2倍
    2. 2、JPG不仅文件更大并且还出现了噪点(如图中红色方框标注的)

    适用PNG的场景1

    那么是什么原因造成这样的差异呢?

    首先我们可以看出“立刻购买”这个按钮是在photoshop中用矢量工具绘制出来的,其渐变填充是非常规则的线性渐变,文字颜色和描边等都是采用纯色,所以这个图像所包含的色彩信息非常有限。根据前面我们介绍的无损压缩的特性,当用PNG存储这个图像时,只需要保存很少的色彩信息就可以真实还原这个图像。而对于JPG格式来说大小主要决定于图像的颜色层次,所以在这种颜色较少但对比强烈的情况下,反而不能很好的压缩文件大小。

    另外根据有损压缩的压缩算法JPG会在图像中通过渐变或其他方式填充一些被删除的数据信息来对图像进行压缩,图中红色和白色的地方由于色差较大,JPG在压缩过程中就会填充一些额外杂色进去,反而影响的图像的质量。这也是为什么JPG不利于存储大块颜色相近区域以及亮度差异十分明显的图像的原因。

    示例2

    我们再来看另外一个应用场景,下图是在淘宝彩票页面使用的一个Banner图像。同样用PNG8和JPG进行了保存,可以发现当用PNG8保存时不仅保证了图像的质量且图像文件的大小仅有8.3K,而当用JPG100%保存时文件大小则增加到44.2K,如果不经放大可能还看不出具体的差异,但实际和前面一样也会出现不必要的噪点。如果我们要达到PNG8的压缩率采用JPG45%进行保存,则图像会出现较严重的失真。

    PNG的应用场景2

    由此我们可以得出结论,具备以下条件的图像更适合用PNG8格式进行存储:

    1. 1、图像上颜色较少,并且主要以纯色或者平滑的渐变色进行填充。
    2. 2、具备较大亮度差异以及强烈对比的简单图像(如“立刻购买”按钮中的背景和文字)。

    根据经验具备上述条件的图像一般是使用photoshop或其他软件中的矢量工具进行绘制然后再保存成位图的图像。

    什么时候应该使用JPG

    示例1

    从JPG的特性介绍我们知道JPG更适合用来存储摄影或写实图像,所以我们不妨先拿一张摄影作品做尝试。

    下图是一副巴士车的照片,我们尝试用JPG 60%(左上)、PNG8 256色 无仿色(右上)、PNG8256色 扩散仿色(左下)、PNG32(右下)分别进行了存储。可以看出当用JPG存储图像时不仅能够达到最大的压缩率,也能尽量保证原图的还原效果。而采用PNG8进行保存时图像文件大小更大,失真也较严重。只有在PNG24的格式下才能保证品质,然而文件大小却比JPG要大很多。

    产生这种结果的原因也与JPG和PNG各自的压缩算法有关。

    对于摄影或者写实作品,由于受环境光线的影响,图像上的色彩层次十分丰富。比如巴士车上的红色区域由于反光、阴影以及透视效果会形成明暗、深浅各异的区域,如果用PNG去保存,则需要不同明暗度的红色去存储这个区域。对于整张图片来说,PNG8的256色无法完全索引图像上出现的所有颜色,于是在存储时就会丢失许多颜色而产生失真。如果要保证图像的效果,则需要色彩范围更广的PNG24进行存储,相应的文件大小也会增加。

    而JPG的压缩算法则更利于对真实世界中这些复杂的色彩变化进行压缩处理,从而在尽量压缩文件大小的情况下比较好的还原图像的视觉效果。

    适用JPG的场景1-1
    适用JPG的场景1-2
    适用JPG的场景1-3

    示例2

    那么是不是只有在存储照片的时候才需要用到JPG呢?我们不妨来看另外一个例子。

    下图是最近比较火爆的某微博页面,在这里我们可以选择不同的风格,每种风格都会有一个非常有特色的背景图片。我们尝试用不同图片格式对背景进行保存时可以发现:当用JPG进行保存时(直接背景另存为)文件大小仅36.3K;而用PNG8256色无仿色去保存时大小增加到57.7K,不仅如此由于颜色的缺失在图像上还出现了一些带锯齿的色块;为了降低这些色块对图像质量的影响我们对PNG8增加了扩散仿色的效果,此时文件大小达到了156.3K;而当采用PNG24完全不失真的保存时文件大小是231.9K。

    尽管这幅背景图也是通过photoshop制作,但我们可以发现由于在图像上采用了很多的真实素材(比如白云、蚂蚁、绿叶等),而这些真实素材和摄影图像一样也会存在非常丰富的色彩层次,所以也不适合用PNG格式进行保存。这个时候我们就应该采用JPG格式。

    适用JPG的场景2-1
    适用JPG的场景2-2

    由此我们可以得出结论:对于写实的摄影图像或是颜色层次非常丰富的图像采用JPG的图片格式保存一般能达到最佳的压缩效果。

    根据经验我们在页面中使用的商品图片、采用人像或者实物素材制作的广告Banner等图像更适合采用JPG的图片格式保存。

    总结

    由此可见在存储图像时采用JPG还是PNG主要依据图像上的色彩层次和颜色数量进行选择。一般层次丰富颜色较多的图像采用JPG存储,而颜色简单对比强烈的则需要采用PNG。但也会有一些特殊情况,例如有些图像尽管色彩层次丰富,但由于图片尺寸较小,上面包含的颜色数量有限时,也可以尝试用PNG进行存储。而有些矢量工具绘制的图像由于采用较多的滤镜特效也会形成丰富的色彩层次,这个时候就需要采用JPG进行存储了。

    另外还有一个原则就是用于页面结构的基本视觉元素,如容器的背景、按钮、导航的背景等应该尽量用PNG格式进行存储,这样才能更好的保证设计品质。而其他一些内容元素,如广告Banner、商品图片等对质量要求不是特别苛刻的,则可以用JPG去进行存储从而降低文件大小。

    3、思考与实践

    什么样的设计更适合web页面?

    慎用较“重”的视觉设计元素

    Web2.0时代网页设计的一大趋势就是越来越“轻”。除了对那些高光和圆角效果的审美疲劳之外,设计师们也开始意识到好的设计应该是内容与形式的完美结合,而非形式的堆砌。所以设计师在应用那些较“重”的视觉效果时,一定要想清楚这样做的目的和意义,以及是否与产品的特点和受众的气质相契合。

    “轻量“设计一个比较典型的例子就是国内某知名网站,几乎没有采用任何需要图片的视觉元素,而是通过简单的CSS样式去实现,这样不仅能够突出内容,更能提升页面的访问速度。所以我十分强烈的建议视觉设计师也掌握一定的html和css知识(尤其是CSS3实现了很多过去需要图片才能实现的效果,例如圆角和渐变),这样在做设计的时候能够全面的去考虑产品效果。

    轻量设计示例

    下面这个电子商务网站则采用过多无意义的视觉元素堆砌,不仅没有实现很好的设计效果,反而由于需要太多的图片元素而影响了页面的性能。

    重度设计

    如果由于产品需要在设计中不得不使用较”重“的视觉元素,我们也可以根据图片格式的特点选择适当的表现形式以达到更好的效果。

    例如在下面这个例子中,第一个Banner应用了更适合PNG格式的设计风格(较多纯色和简单渐变的应用)不仅能达到热烈、突出的视觉效果,在保证图片质量的同时也更好的压缩了文件大小;而第二个Banner由于应用了过于复杂的渐变色和强烈对比,并且在局部区域采用太多的高光和阴影效果,导致图片的色彩层次过多,不论采用PNG还是JPG格式保存都无法实现图像质量和文件大小的最优化。

    设计风格选择

    当然举这个例子并不是要设计师在做设计的时候过分考虑页面性能问题,而是要清楚不同设计形式的效果和实现成本,在设计过程中多问自己为什么要这样做?

    内容和形式的分离

    对于一些比较强调视觉效果的特殊产品,比如活动推广页面或MiniSite。我们也可以利用图片格式和一些前端知识对设计进行优化,比较常用的一种设计方法就是内容和形式的分离。

    如下面一些国外比较流行的设计风格,以及前面提到的微博网站,都是通过大幅的背景图去进行意境的传达和气氛的渲染。这样做的一个好处就是能把需要用到图片的视觉元素进行集中的压缩优化,同时又不会由于加载太慢而影响用户的访问速度(前端实现时一般会用相近的背景色先进行填充,然后在逐步显示背景图)。

    内容和形式分离1
    内容和形式分离2
    适用JPG的场景2-1

    个人认为好的视觉设计最重要是意境传达和气氛渲染,使用高光、阴影等滤镜效果的目的也仅仅是为了达到质感上的统一从而更好的去传达意境和渲染气氛,所以切忌为了质感而质感。即便是为了体现设计品质也不一定非要使用炫丽的滤镜,相较而言优美的布局和精巧的结构才是体现品质的关键!

    通过较小的视觉牺牲换取较大的性能提升

    有时候为了提升页面的加载速度达到更好的用户体验,不得不对设计进行优化。这个时候利用我们对图片格式知识掌握就可以更有目的性的去进行优化。

    例如下图是淘宝“双十一”大促活动的一个页头设计,由于页面访问量非常大并且要使用较多的商品图片,不得不对页头设计进行优化以提升性能。这个时候我们就可以去掉一些不太重要的高光、渐变和阴影效果,从而大大降低文件大小。

    设计优化

    我们还可以做些什么?

    Sprite图片二次优化

    由于目前国内很多互联网公司没有页面重构师这一职位,所以页面切图和静态代码实现基本都是前端工程师去完成。为提高页面性能目前普遍采用的实现方式是将与页面结构相关的需要用到图片的视觉元素集中在一个PNG图片上,然后通过CSS样式将其应用到页面中,我们称这个图片为Sprite图片。由于这个图片上经常要集中较多的视觉元素,在用PNG格式存储时难免会产生失真而影响图片质量。这个时候就需要视觉设计师帮助前端开发工程师对Sprite图片进行优化,这样做的好处是不仅能提升图片质量,还能达到减小文件大小的效果,可谓一举两得。

    下图是一个优化前的Sprite图片,由于视觉元素过多PNG8无法真实保存所有的颜色信息,于是便会产生颜色的缺失和杂色的产生(如局部放大图所示)。

    Sprite优化1

    在前端工程师完成页面的静态代码之后,视觉设计师可以将定位好的Sprite图片进行像素级的优化,去掉不必要的杂色,并且用已存在的索引色对缺失的地方进行补充,这样不仅能压缩文件大小,还能提升设计品质。

    Sprite优化2

    Sprite图片的优化方式有很多种,比如通过索引色排序进行颜色的删减和替换,或是直接通过像素描绘进行优化。设计师可以根据具体的场景进行选择和处理。

    以上设计和优化的方法只是我在工作中一些经验的积累和总结,个人感觉每一个点展开来都有很多值得研究和讨论的地方,限于篇幅有限不能继续深入。关于图片优化的高级技巧有两篇比较经典的文章推荐给大家:Clever PNG Optimization TechniquesClever JPEG Optimization Techniques

    4、附录-Photoshop中各种参数的含义及设置技巧

    PNG8的参数设置

    PNG8参数设置

    减低颜色深度算法与颜色

    指定用于生成颜色查找表的方法,以及想要在颜色查找表中使用的颜色数量。可以选择以下减低颜色深度算法之一:

    1. 1、可感知:通过为人眼比较灵敏的颜色赋以优先权来创建自定颜色表。
    2. 2、可选择:创建一个颜色表,此表与”可感知”颜色表类似,但对大范围的颜色区域和保留Web 颜色有利。此颜色表通常会生成具有最大颜色完整性的图像。“可选择”是默认选项。
    3. 3、随样性:通过从图像的主要色谱中提取色样来创建自定颜色表。例如,只包含绿色和蓝色的图像产生主要由绿色和蓝色构成的颜色表。大多数图像的颜色集中在色谱的特定区域。
    4. 4、受限(Web):使用 Windows 和 Mac OS 8 位(256 色)调板通用的标准 216色颜色表。该选项确保当使用 8 位颜色显示图像时,不会对颜色应用浏览器仿色。(该调板也称为 Web 安全调板。) 使用 Web调板可能会创建较大的文件,因此,只有当避免浏览器仿色是优先考虑的因素时,才建议使用该选项。
    5. 5、自定:使用用户创建或修改的调色板。如果打开现有的GIF 或 PNG-8 文件,它将具有自定调色板。使用”存储为 Web和设备所用格式”对话框中的”颜色表”调板可自定颜色查找表。
    6. 6、黑白、灰度、Mac OS、Windows使用一组调色板。

    建议:一般情况下默认选择“可选择”项即可。

    减低颜色深度算法设置

    仿色方法和仿色

    确定应用程序仿色的方法和数量。”仿色”是指模拟计算机的颜色显示系统中未提供的颜色的方法。

    较高的仿色百分比使图像中出现更多的颜色和更多的细节,但同时也会增大文件大小。为了获得最佳压缩比,请使用可提供所需颜色细节的最低百分比的仿色。

    若图像所包含的颜色主要是纯色,则在不应用仿色时通常也能正常显示。包含连续色调(尤其是颜色渐变)的图像,可能需要仿色以防止出现颜色条带现象。

    可以选择以下几种仿色方法之一:

    1. 1、扩散:应用与”图案”仿色相比通常不太明显的随机图案。仿色效果在相邻像素间扩散。
    2. 2、图案:使用类似半调的方形图案模拟颜色表中没有的任何颜色。
    3. 3、杂色:应用与”扩散”仿色方法相似的随机图案,但不在相邻像素间扩散图案。使用”杂色”仿色方法时不会出现接缝。

    建议:一般只在图片颜色过多产生失真的情况下才需要选择仿色。建议选择扩散仿色,可以适当调节仿色的百分比以达到最佳的效果。仿色度越高文件大小也越大。

    仿色设置

    透明度和杂边

    确定如何优化图像中的透明像素。

    1. 1、要使完全透明的像素透明并将部分透明的像素与一种颜色相混合,请选择”透明度”,然后选择一种杂边颜色。
    2. 2、要使用一种颜色填充完全透明的像素并将部分透明的像素与同一种颜色相混合,请选择一种杂边颜色,然后取消选择”透明度”。
    3. 3、要选择杂边颜色,请单击”杂边”色板,然后在拾色器中选择一种颜色。或者,也可以从”杂边”菜单中选择一个选项:”吸管”(使用吸管样本框中的颜色)、”前景色”、”背景色”、”白色”、”黑色”或”其它”(使用拾色器)。

    建议:强烈建议由视觉设计师根据实际应用场景在保存之前就处理好透明图像的背景。

    透明度杂边设置

    交错

    PNG、GIF这两种图像格式都提供了一种功能,让图像能够更快地显示。图像可以以一种特殊方式存储,显示时先大概显示图像的草图,当文件全部下载后再填充细节。这起到一种很有意义的心理效果,因为这样使人们有东西可看,而不必坐着干等大型图像慢慢显示在屏幕上。

    建议:对于尺寸和文件大小相对较大的图片建议勾选此项。

    JPG的参数设置

    JPG参数设置

    品质

    从”品质级别”菜单中选取一个选项,或者在”品质”文本框中指定一个值。”品质”设置越高,压缩算法保留的细节越多。但是,使用高”品质”设置比使用低”品质”设置生成的文件大。查看几种品质设置下的优化图像,确定品质和文件大小之间的最佳平衡点。

    品质设置技巧

    1. 1、不要存100%品质的JPG格式图片。因为100%并不一定是最高的品质,而是一个优化算法的极限值,所以会增加不必要的文件大小。建议存储95%品质的图片就可以最大限度的降低失真。
    2. 2、谨慎使用50%品质以下的压缩率。使用50%以下品质存储时会采用额外的压缩算法而导致图片失真更严重,尤其是对于有高对比度的图片。

    优化

    选择”优化”创建文件大小稍小的增强型 JPEG。建议使用”优化 JPEG”格式以获得最大文件压缩量;但是,一些较旧的浏览器不支持此特性。

    建议:建议勾选此项,目前基本已经不存在不支持改功能的浏览器。

    连续

    选择”连续”创建在 Web 浏览器中连续显示的图像。图像将显示为一系列的叠加,使查看者在整个图像下载完毕之前,能够看到图像的低分辨率版本。连续 JPEG 需要更多的内存用于查看,一些浏览器不支持该选项。

    建议:勾选此项在某些情况下可压缩文件大小(图片大小大于10k时),某些情况下会增大文件大小,建议在保存是根据实际情况决定。不过IE6及更早版本的IE浏览器不支持JPG连续显示,而是在图片完全加载后一次成像,用户体验上可能还不如不使用连续的逐步成像要好,所以建议慎选此项。

    模糊

    指定应用于图像的模糊量。”模糊”选项应用与”高斯模糊”滤镜相同的效果,并允许进一步压缩文件以获得更小的文件大小。建议使用 0.1 到0.5 之间的设置。

    ICC 配置文件

    选择”ICC 配置文件”将图片的 ICC 配置文件与文件保留在一起。ICC配置文件由某些浏览器用于色彩校正。(请参阅Photoshop中设置色彩管理。)

    杂边

    指定原稿图像中透明像素的填充色:点按”杂边”色板,然后在拾色器中选择一种颜色。从”杂边”菜单中选取选项。原稿图像中完全透明的像素由选中的颜色填充,原稿图像中部分透明的像素与选中的颜色相混合。

     

    转载自:https://blog.csdn.net/minggeqingchun/article/details/78748550

    展开全文
  • Exif图片方向的一些发现

    千次阅读 2018-10-14 22:27:23
    背景 首先,先要了解Exif是个什么东东,搬出...说到底Exif就是一种格式,用来存储图片的一些信息,这些信息和我们日常比较相关的有拍摄设备,拍摄地点,图片尺寸等,不过今天的主角是另外一个——那就是图片方向(or...

    背景

    首先,先要了解Exif是个什么东东,搬出百度百科

    可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。

    说到底Exif就是一种格式,用来存储图片的一些信息,这些信息和我们日常比较相关的有拍摄设备,拍摄地点,图片尺寸等,不过今天的主角是另外一个——那就是图片方向(orientation)。这个图片方向不是指我们平时使用图片编辑器旋转的方向,而是拍照时手机的方向。总共有八个方向:
    在这里插入图片描述

    下图是JPEG ORIENTATION对应图片方向的纠正算法,这里它通过三位二进制数代表八种方向,然后再通过每一位二进制数对应不同的操作来对图片进行纠正,如下:
    在这里插入图片描述

    最高位二进制数代表对角线翻转的操作,第二位二进制数代表旋转180度的操作,最低位代表水平翻转的操作。
    例如001,就是水平翻转,所以可以看到001的图形和原图形关于水平轴对称。通过把八个方向的图形用3个二进制数即三种操作组合,就可以很方便的对图形做转换,编码伪代码如下:

    if (value & 100b != 0)  image.flip-diagonally
    if (value & 010b != 0)  image.rotate-180
    if (value & 001b != 0)  image.flip-horizontally
    

    那有人就会困惑了,自己怎么平时没有看到这种图片呢,这是因为我们使用的图片查看器或者是浏览器对orientation做了兼容,会对展示的图片做转换。

    如下是windows文件夹的展示:

    下面则是Android Studio的图片展示

    所以可以看到,windows是默认对图片orientation做了处理,而Android的ImageView则没有处理所以看到的是图片本来的方向。
    这是八个F的图片链接

    应用

    在Android里面,三星手机的拍照是个奇葩的存在,三星手机的exif是旋转90度,别家手机则是0度,所以三星手机的照片需要做处理,这里是一张三星手机照片的exif信息:
    在这里插入图片描述

    三星手机的方向是Rotate 90CW,意思就是需要顺时针方向(ClockWise)旋转90度。
    脑壳转的快的同学可以对照上面的F图,相信很快看出是101这张图。
    那我们取出图片的orientation值进行验证:

            try {
                val exifInterface = ExifInterface(resources.openRawResource(id))
                val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                Log.e("orientation", orientation.toString())
            } catch (e: IOException) {
                e.printStackTrace()
            }
    

    打印结果是6,和上面的101对不上,其实在Android的orientation是需要做减1处理的,也就是说6其实对应的是101这种状态。另外,需要注意的是,如果打印结果是0,那么说明图片没有orientation这个信息。

    那接下来我们进行编码,这是第一张方式:

      val options = BitmapFactory.Options()
      var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.error_orientation, options)
      val matrix = Matrix()
      matrix.postRotate(getOrientation(R.mipmap.error_orientation).toFloat())
      bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
      imageview.setImageBitmap(bitmap)
    
      private fun getOrientation(id:Int): Int {
            var degree = 0
            try {
                val exifInterface = ExifInterface(resources.openRawResource(id))
                val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                when (orientation) {
                    ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
                    ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
                    ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
                }
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return degree
        }
    

    一般来说,我们只需要处理这三种角度,上面三个角度对应的orientation是6 3 8,也就是101,010,111这三种状态。为什么一般只需要处理这三种状态呢,自己脑补一下拿相机的角度,不外乎就四种情况,除了正常的情况下,不就只需要处理三种情况吗?嘿嘿,我真是个小机灵鬼。
    当然,如果要严谨一点,还是需要按照JPEG那种操作方式来,如下:

     val options = BitmapFactory.Options()
     var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.f7t, options)
     val matrix = genOrientationMatrix(R.mipmap.f7t)
     bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
     imageview.setImageBitmap(bitmap)
    
     private fun genOrientationMatrix(id:Int): Matrix {
            val matrix = Matrix()
            try {
                val exifInterface = ExifInterface(resources.openRawResource(id))
                var orientation =  exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                if (orientation > 0) {
                    orientation--
                    if (orientation and 0b100 != 0) { //对角线翻转
                        matrix.postScale(-1.0f, 1.0f)
                        matrix.postRotate(-90f)
                    }
                    if (orientation and 0b010 != 0) { //旋转180度
                        matrix.postRotate(180f)
                    }
                    if (orientation and 0b001 != 0) { //水平翻转
                        matrix.postScale(-1.0f, 1.0f)
                    }
                }
                return matrix
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return matrix
        }
    
    

    其实就是将JPEG对于orientation的转换利用代码进行实现,对矩阵进行相应的变换。

    总结

    Exif是一种存储了相片一些信息的格式,平常我们在进行Android开发的时候,一般需要考虑方向的问题,但是在日常生活,这个也是暴露我们隐私的入口,所以手机在拍照的时候,最好将保存位置这些选项关闭,避免泄漏自己的隐私。

    参考

    Android性能优化:图片保存,还能更快

    JPEG Orientation

    展开全文
  • Retrofit2实现图片文字上传

    万次阅读 热门讨论 2018-07-22 16:39:14
    3.2、准备好选择的图片 3.3、开始构造参数 3.4、实现上传 附:UploadHelper.java源码 前言 距离上一篇文章到现在已经有将近半年的时间了,因为换了一座城市,到现在才算是刚刚熟悉起来吧,所以这段时间一直没能...

    目录

     

    前言

    一、效果展示

    二、基本配置

    三、代码实战

    3.1、创建RetrofitManager和APIService

    3.2、准备好选择的图片

    3.3、开始构造参数

    3.4、实现上传

    附:UploadHelper.java源码


    前言

    距离上一篇文章到现在已经有将近半年的时间了,因为换了一座城市,到现在才算是刚刚熟悉起来吧,所以这段时间一直没能静下心来去总结,今天是周末,首先祝大家都能度过一个开心快乐的周末时光,我这个屌丝只能坐在家里码码文字了。今天总结一下关于在Android开发中使用Retrofit2实现类似于Web端表单文件上传的技术实现。

    工作了也有将近三年的时间了,这期间关于这一块的实现方法也是换了几波,从最开始使用的基于Volley开发的MultipartRequest,到后来的OkHttp3,再到现在使用的Retrofit2,总结下来,其实都是大同小异。客户端这边主要就是构造请求参数,不同的参数需要指定其对应的参数类型,具体体现就是在构造参数时,需要指定Content-Type的类型(如果你平时观察过http请求,肯定会在Request Headers就是请求头中看到过它,我下面的日志截图里也有),然后构造完参数以后,剩下的就是和发送普通的Http请求一样了。服务端会有一个叫做enctype的东西,它的作用是告知服务器请求正文的MIME类型(和请求消息头的headers作用一样),然后服务端就根据对应的类型去解析,比如普通文字类型直接根据对应实体中的字段获取对应的值,文件类型的通过IO流读取写入文件,这样就完成了整个的上传流程。那么接下来,就来具体说说Android客户端Retrofit2的实现过程,至于服务端因为我懂得不多,所以就不在这里丢人了,简单点的大家可以尝试着写个Servlet去测试测试。

    一、效果展示

    这里录了一个gif效果图,可以很清晰的看到上传的结果,我的网速太快了,我都没看清:

    不过确实是上传成功了,来看一下日志:

    二、基本配置

    既然是图文上传,这里重点讲的是上传,所以图片选择这一块我不打算多讲了,大家可以根据自己的实际业务去寻找对应的图片选择器(github上有很多),我这里使用的是MultiImageSelector,我把它的代码下载下来了,在本地项目中新建了一个Android Library,并给项目主Module添加上依赖,这样可以根据自己的需要进行定制,网络库这里肯定是选择Retrofit2.x版本了,直接添加对应的依赖到build.gradle文件中即可,结构如下图所示:

    另外给大家推荐几款我觉得还不错的图片选择器的开源库,我这里因为没那么多需求,所以选了上面这个体积比较小的:

    GalleryFinal:https://github.com/pengjianbo/GalleryFinal

    PhotoPicker:https://github.com/donglua/PhotoPicker

    Matisse(知乎开源的小清新款):https://github.com/zhihu/Matisse

    三、代码实战

    3.1、创建RetrofitManager和APIService

    在最初的http协议中,没有定义上传文件的Method,为了实现这个功能,http协议组改造了post请求,添加了一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=bound,其中bound,其中{bound}是定义的分隔符,用于分割各项内容(文件,key-value对),不然服务器无法正确识别各项内容。post body里需要用到,尽量保证随机唯一。Retrofit是个网络代理框架,负责封装请求,然后把请求分发给http协议具体实现者-httpclient。retrofit默认的httpclient是okhttp。Retrofit会根据注解封装网络请求,待httpclient请求完成后,把原始response内容通过转化器(converter)转化成我们需要的对象(object)。那么Retrofit和okhttp怎么封装这些multipart/form-data上传数据呢?答案如下:

    @retrofit2.http.Multipart: 标记一个请求是multipart/form-data类型,需要和@retrofit2.http.POST一同使用,并且方法参数必须是@retrofit2.http.Part注解。 

    @retrofit2.http.Part: 代表Multipart里的一项数据,即用${bound}分隔的内容块。 
    @retrofit2.http.PartMap:用于表单字段,默认接受的类型是Map<String,RequestBody>,可用于实现多文件上传。

    了解了上面这些,就可以开始定义我们的ApiService了,注意这是个接口类,定义如下:

    返回值类型这里Retrofit返回的是Call类型,如果你是使用的RxJava,这里直接返回Observable就行了,泛型这里传入的是服务端返回内容的一个实体类,这样最后直接根据实体类中的字段去进行结果的处理就OK了!

    定义完了接口,接下来我们再去定义一个Retrofit的处理类,当然你也可以不定义,直接根据Retrofit的官方api在用到的地方调用也行,但是实际项目中肯定是要封装一个类,不然要写多少遍啊,不符合我们CV工程师的特点了!关于Retrofit的用法不再细说了,你既然能看到这里了,肯定不是个小白了,直接上代码:

    package com.nari.yihui.api;
    
    import android.support.annotation.NonNull;
    import android.util.Log;
    
    import com.nari.yihui.BaseApplication;
    import com.nari.yihui.commonlib.ui.smartshow.SmartToast;
    import com.nari.yihui.constants.ApiConstant;
    import com.nari.yihui.helper.UserManager;
    import com.nari.yihui.utils.CommonUtil;
    
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;
    
    import okhttp3.Interceptor;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    import okhttp3.logging.HttpLoggingInterceptor;
    import retrofit2.Retrofit;
    import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
    import retrofit2.converter.gson.GsonConverterFactory;
    
    /**
     * Created by 纪安奇 on 2018/6/11.
     * Retrofit的管理类
     */
    
    @SuppressWarnings("FieldCanBeLocal")
    public class RetrofitManager {
    
        //读超时时长,单位:毫秒
        public static final int READ_TIME_OUT = 15 * 1000;
        //写超时时长,单位:毫秒
        public static final int WRITE_TIME_OUT = 15 * 1000;
        //连接超时时长,单位:毫秒
        public static final int CONNECT_TIME_OUT = 15 * 1000;
    
        private static Retrofit mRetrofit; //Retrofit对象
        private static ApiService mApiService; //接口类实例对象
        private static OkHttpClient mClient; //OKHttp对象
    
        //获取ApiService对象
        public static ApiService getInstance() {
            if (CommonUtil.isNetworkAvailable(BaseApplication.getAppContext())) {
                //配置OkHttpClient
                OkHttpClient.Builder builder = new OkHttpClient.Builder();
                builder.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
                        .writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
                        .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
                        .addInterceptor(getLoggerInterceptor());
                //添加请求头Header
                if (UserManager.getInstance().getUser() != null) {
                    builder.addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(@NonNull Chain chain) throws IOException {
                            Request request = chain.request()
                                    .newBuilder()
                                    .addHeader("Cookie", "sid=" + UserManager.getInstance().getUser().getObj().getSid())
                                    .addHeader("Content-Type", "text/html;charset:utf-8")
                                    .build();
                            return chain.proceed(request);
                        }
                    });
                }
                mClient = builder.build();
                //配置Retrofit
                mRetrofit = new Retrofit.Builder()
                        .baseUrl(ApiConstant.baseUrl)
                        .addConverterFactory(GsonConverterFactory.create())
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .client(mClient)
                        .build();
                //创建ApiService对象
                mApiService = mRetrofit.create(ApiService.class);
            } else {
                SmartToast.showInCenter("当前网络连接失败");
            }
            return mApiService;
        }
    
        //设置日志拦截器,打印返回的数据
        private static HttpLoggingInterceptor getLoggerInterceptor() {
            //日志显示级别
            HttpLoggingInterceptor.Level level = HttpLoggingInterceptor.Level.BODY;
            //新建log拦截器
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(@NonNull String message) {
                    Log.e("ApiUrl------>", message);
                }
            });
            loggingInterceptor.setLevel(level);
            return loggingInterceptor;
        }
    
    }

    3.2、准备好选择的图片

    既然是图片文字上传,首先我们肯定得准备好图片和文字,上面已经大致说了怎么去实现图片的选择,这里不会详细的说,因为篇幅有限,所以只讲如何上传。首先来看一下服务端需要我们如何拼接参数去提交的呢?

    可以看到输入的意见文字字段为content,它需要我们和其它两部分内容一起拼接成一个json串然后提交,图片部分需要我们按照key-value的形式进行提交,key就是file,value就是我们对应的图片,有几张图片就有几组file=?,然后这两大部分一起构造成最后需要提交的参数。在我的项目中我把最后需要上传的图片文件都存放到了一个File[]类型的数组中,那么根据服务端的要求也就会写成(file,file[0])这种成对的形式。

    3.3、开始构造参数

    有了上面的思路之后,就开始干吧。我在本地创建了一个UploadHelper类,初步的构想是将这个类采用单例模式的形式,然后对于参数这部分采用建造者模式,这样方便参数的添加,后面我会给出这个类的代码,这里就先截图了:

    整个类采用单例模式:

    对于参数部分,采用建造者模式进行构建:

    我这里对于Map的value类型做了一个判断,文本和图片会根据对应的类型添加到Map中,是不是很方便,现在可以去真正的构造参数了,请看代码:

    首先构造json串:

    String jsonString = "{\"name\":\"" + name + "\",\"content\":\"" + content +
                                "\",\"province\":\"" +
                                BaseApplication.getSpUtil().getString(SPConstant.SP_PROVINCE) + "\"}";

    接着构造图片,并且合并上面的json串:

    helper = UploadHelper.getInstance();
            if (bmp.size() == 1) {
                helper.addParameter("file", files[0]);
            } else if (bmp.size() == 2) {
                helper.addParameter("file", files[0]);
                helper.addParameter("file", files[1]);
            } else if (bmp.size() == 3) {
                helper.addParameter("file", files[0]);
                helper.addParameter("file", files[1]);
                helper.addParameter("file", files[2]);
            }
            Map<String, RequestBody> params = helper.addParameter("param", json).builder();

    3.4、实现上传

    最后是发送我们的Http请求,实现上传的功能,这里根据处理结果做一些UI上的更新:

    LoadingDialog.show(this, "正在上传,请稍候...", false);
            ApiConstant.baseUrl = ApiConstant.getBaseUrl(BaseUrlCode.FEEDBACK_UPLOAD);
            RetrofitManager.getInstance().uploadFeedback(params).enqueue(new Callback<CommonJsonModel>() {
                @Override
                public void onResponse(@NonNull Call<CommonJsonModel> call, @NonNull Response<CommonJsonModel> response) {
                    if (response.isSuccessful()) {
                        LoadingDialog.canceled();
                        if (response.body() != null) {
                            if (response.body().getCode() == 1000) {
                                SmartToast.showInCenter("上传成功");
                                finish();
                                overridePendingTransition(R.anim.in_from_right, R.anim.finish_to_right);
                            } else if (response.body().getCode() == 2004 || response.body().getCode() == 2006) {
                                SmartToast.showInCenter("登录失效,请重新登录");
                                CommonUtil.loginOut(FeedBackActivity.this);
                            } else {
                                SmartToast.showInCenter(response.body().getMsg());
                            }
                        }
                    }
                }
    
                @Override
                public void onFailure(@NonNull Call<CommonJsonModel> call, @NonNull Throwable t) {
                    LoadingDialog.canceled();
                    LogUtil.e("意见反馈上传图文异常------" + t.getMessage());
                }
            });

    以上就是整个图文上传的实现过程,如果有不对的地方,还请告知,谢谢大家!

    附:UploadHelper.java源码

    package com.nari.yihui.helper;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.net.Uri;
    import android.os.Environment;
    import android.provider.MediaStore;
    
    import com.nari.yihui.commonlib.imageselector.MultiImageSelectorActivity;
    import com.nari.yihui.constants.Constant;
    import com.nari.yihui.utils.FileUtils;
    import com.nari.yihui.utils.LogUtil;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import okhttp3.MediaType;
    import okhttp3.RequestBody;
    
    /**
     * 包名:com.nari.yihui.helper
     * 文件名:   UploadHelper
     * 创建时间:   2018/7/19 18:50
     * 作者:     纪安奇
     * 作用:图文上传工具类
     */
    public class UploadHelper {
        private volatile static UploadHelper mInstance;
        public static Map<String, RequestBody> params;
        private Uri photoUri;
    
        private UploadHelper() {}
    
        //单例模式
        public static UploadHelper getInstance() {
            if (mInstance == null) {
                synchronized (UploadHelper.class) {
                    if (mInstance == null)
                        mInstance = new UploadHelper();
                    params = new HashMap<>();
                }
            }
            return mInstance;
        }
    
        //根据传进来的Object对象来判断是String还是File类型的参数
        public UploadHelper addParameter(String key, Object o) {
            RequestBody body = null;
            if (o instanceof String) {
                body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
            } else if (o instanceof File) {
                body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
            }
            params.put(key, body);
            return this;
        }
    
        //建造者模式
        public Map<String, RequestBody> builder() {
            return params;
        }
    
        //清除参数
        public void clear(){
            params.clear();
        }
    
        //最终上传的Bitmap保存为File对象
        public void saveBitmapFile(List<Bitmap> mList,File[] files) {
            String root = Environment.getExternalStorageDirectory().toString();
            File myDir = new File(root + "/suggestionUpload");
            if (myDir.exists()) {
                myDir.delete();
            }
            myDir.mkdirs();
            for (int i = 0; i < mList.size(); i++) {
                files[i] = new File(myDir, "ims" + i + ".JPEG");
                try {
                    if (files[i].exists()) {
                        files[i].delete();
                    }
                    files[i].createNewFile();
                    FileOutputStream out = new FileOutputStream(files[i]);
                    mList.get(i).compress(Bitmap.CompressFormat.JPEG, 100, out);
                    out.flush();
                    out.close();
                    LogUtil.e("最终上传图片的路径------>" + files[i].getAbsolutePath());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        //启用裁剪
        public void startPhotoZoom(Activity mContext, List<String> drr) {
            try {
                // 获取系统时间 然后将裁剪后的图片保存至指定的文件夹
                @SuppressLint("SimpleDateFormat")
                SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
                String address = sDateFormat.format(new Date());
                if (!FileUtils.isFileExist("")) {
                    FileUtils.createSDDir("");
                }
                drr.add(FileUtils.SDPATH + address + ".JPEG");
                @SuppressLint("SdCardPath")
                Uri imageUri = Uri.parse("file:///sdcard/formats/" + address + ".JPEG");
                final Intent intent = new Intent("com.android.camera.action.CROP");
                // 照片URL地址
                intent.setDataAndType(photoUri, "image/*");
                intent.putExtra("crop", "true");
                intent.putExtra("aspectX", 1);
                intent.putExtra("aspectY", 1);
                intent.putExtra("outputX", 480);
                intent.putExtra("outputY", 480);
                // 输出路径
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                // 输出格式
                intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
                // 不启用人脸识别
                intent.putExtra("noFaceDetection", false);
                intent.putExtra("return-data", false);
                mContext.startActivityForResult(intent, Constant.REQUEST_CUTTING_CODE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //拍照
        public void takePhoto(Activity mContext) {
            try {
                Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                String sdcardState = Environment.getExternalStorageState();
                String sdcardPathDir = Environment.getExternalStorageDirectory().getPath() + "/myImage/";
                File file = null;
                if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
                    // 有sd卡,是否有myImage文件夹
                    File fileDir = new File(sdcardPathDir);
                    if (!fileDir.exists()) {
                        fileDir.mkdirs();
                    }
                    // 是否有headImg文件
                    file = new File(sdcardPathDir + System.currentTimeMillis() + ".JPEG");
                }
                if (file != null) {
                    String path = file.getPath();
                    photoUri = Uri.fromFile(file);
                    openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
                    mContext.startActivityForResult(openCameraIntent, Constant.REQUEST_CAMERA_CODE);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        //从相册选择
        public void albmSelect(Activity mContext,int maxCanSelectNum,List<String> drr) {
            int selectedMode = MultiImageSelectorActivity.MODE_MULTI;
            int maxNum = maxCanSelectNum - drr.size();
            Intent intent = new Intent(mContext, MultiImageSelectorActivity.class);
            // 是否显示拍摄图片
            intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, false);
            // 最大可选择图片数量
            intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, maxNum);
            // 选择模式
            intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, selectedMode);
            mContext.startActivityForResult(intent, Constant.REQUEST_ALBUM_CODE);
        }
    
    }
    

     

    展开全文
  • PANTONE潘通公布2020年度代表色!

    千次阅读 2019-12-07 11:38:39
    图片来源:PANTONE潘通 色彩预测权威机构PANTONE潘通 都会公布下一年度的代表色系 而离2020也还有不到一个月的时间 PANTONE潘通就在昨天 公布了2020年度代表色 ——PANTONE 19-4052 Classic Blue(经典蓝...
  • 销售代表应该随身携带的销售工具* 产品目录* 已缔结并投入使用的客户名录* 图片及公司画册* 地图* 名片* 客户档案* 计算器* 笔记用具* 最新价格表* 带有公司标识的拜访礼品* 空白“合同申请表”、“拜访记录表”等...
  • Android开源图片加载框架

    千次阅读 2015-05-08 11:17:56
     本文主要介绍了android现下流行的图片加载库中的3个,通过读取源码,比较设计架构,来总结移动应用中图片加载的共同特点,同时比较这三个库的设计模式,博取众之长,补己之短,第一篇文章,词法不好之处还请海涵...
  • 最近在研究MP3中图片提前,发现这篇文章,转到自己博客,留用。原文出处http://blog.csdn.net/studywithallofyou/article/details/7738785 看到标题估计有人懵了,不过对于用到的人可以说是眼前一亮啊。做了点这...
  • 百思不得姐之图片处理(保存与下载)

    千次阅读 2016-04-20 01:59:20
    一 功能图二 讲解思路1 回顾上一篇内容2 创建加载图片类(同时创建xib)3 点击图片查看大图4 点击查看大图(查看长图)5 model出展示图片的控制器6 保存图片7 封装根据网络状态展示不同的图片三 回顾上一篇内容1 上一篇...
  • //此函数最后返回的代表进度条的对象。 var done = 0, length, outline, bar;//声明内部变量。 bar = document.getElementById('done');//进度条中绿色的变化部分。 length = 80; //重置进度到零。 ...
  • 关于图片三级缓存

    千次阅读 2016-06-02 20:58:48
    三级缓存图片实现   三级缓存出现的原因 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到...
  • 在当前这个互联网的时代,不管何种...当然这并不代表,就必须得弄一个特别NB的图片服务架构,简单,高效,稳定就行。所以今天就来总结一个特别简单,高效的图片服务架构:通过共享存储的方式来实现图片服务架构。  
  • ffmpeg图片与视频命令笔记

    千次阅读 2020-07-25 15:04:07
    本篇除了会涉及到音频相关参数,但不会有音频相关操作或者详细解析,主要是针对图片与视频本身进行命令总结,不过在此之前,我想总结一下图片和视频的一些相关介绍。 yuv与RGB图片 关于RGB,它的色彩模式是工业界的...
  • jpg,png,gif图片格式的区别和优劣

    千次阅读 2013-10-03 15:53:59
    为什么想整理这方面的类容,我觉得就像油画要了解他的颜料和画布、雕塑要了解他的石材一样,作为网页设计师也应该对图片格式的特性有一定了解,这样才能更好的表达你的创意和想法。 除此之外,我们在平时工作...
  • JQuery,JS实现图片轮播

    千次阅读 2020-01-14 22:26:31
    JQuery实现图片轮播 JS代码: <script> var imgCount = 5;//定义图片数量 var index = 1; var intervalId; var buttonSpan = $('.pointer')[0].children;//pointer的第0个圆点开始获取它的孩子元素 ...
  • 在当前这个互联网的时代,不管...当然这并不代表,就必须得弄一个特别NB的图片服务架构,简单,高效,稳定就行。所以今天就来总结一个特别简单,高效的图片服务架构:通过共享存储的方式来实现图片服务架构。  
  • node.js 用户上传图片到服务器

    千次阅读 2017-08-24 10:35:13
     我是做java后台开发,之前对node.js 不太了解偏偏这个公司却用了这们新技术,所以短时间内去了解了一下。使用了一段时间发现node.js 确实强大,为什么这样说呢?那就简单的跟大家说一下吧,大家都知道加载顺序从...
  • 微信小程序连接蓝牙打印机打印图片示例

    千次阅读 热门讨论 2020-01-22 11:30:18
    小程序连接蓝牙打印机打印文本与二维码等示例在 github 上都能找到一些,唯独打印图片这个案例几乎没有。最早在 CSDN 找到 微信小程序蓝牙连接 TSPL 打印机打印图片思路 这篇文章,代码完整性很缺,被坑一周。继续找...
  • NewTech:人类发布史上首张黑洞照片—1+17张高清图片讲解黑洞简史 导读 科技圈大事件:天文学捕获首张黑洞照片。爱因斯坦曾精准语言,霍金曾发现黑洞辐射,一个令众多科学期待已久的黑洞,终于现身了。 ...
  • 其中四个字母分别指青(Cyan)、洋红(Magenta)、黄(Yellow)、黑(Black)(K取Black最后一个字母防止和Blue混淆),在印刷中代表四种颜色的油墨。CMYK模式在本质上与RGB模式没有什么区别,只是产生色彩的原理不同...
  • 现在无论是写文章、做 PPT 还是找壁纸、换头像,搜图片都是件费时却最出效果的事情。之前可能说过小编是做自媒体这一块的,关于图片素材这一块来说,需求量还是蛮大的,所以说,之前介绍的部分网站对于我来说是远远...
  • 在过去的几年里,网上出现了数以百计的高质量免费图片网站,但是Pexels正在致力于主办以多样化为重点的图像和视频,这些图像和视频代表了广泛的体验。WordPress.com正在与他们和其他合作伙伴合作,为用户提供更多样...
  • 柯南君的公司最近产品即将上线,由于产品业务对图片的需求与日俱增,花样百出,与此同时,在大数据时代,大流量的冲击下,对图片服务器的压力可想而知,那么今天,柯南君结合互联网的相关热文,加上自己的一点实践...
  • 图片格式与设计那点事儿

    千次阅读 2012-10-07 09:31:44
    为什么想整理这方面的类容,我觉得就像油画要了解他的颜料和画布、雕塑要了解他的石材一样,作为网页设计师也应该对图片格式的特性有一定了解,这样才能更好的表达你的创意和想法。 除此之外,我们在平时工作中...
  • JPEG图片存储格式及原理

    万次阅读 2012-10-27 01:24:00
     JPEG的压缩原理 JPEG的压缩原理其实上面介绍的那些原理的综合,博采众之长,这也正是JPEG有高压缩比的原因。其编码器的流程为: 图9.3 JPEG编码器流程 解码器基本上为上述过程的逆过程: 图9.4 解码器流程 8×...
  • 手性(Chirality)代表着单个图片的翻转不对称性,而视觉手性(Visual Chirality)则是针对图像分布(Image Distribution)所定义的翻转不对称性。 假设一个图像分布中包含了右手和左手的照片(左右手的图片出现...
  • 今天我们来介绍一下图片检索技术,图片检索就是拿一张待识别图片,去从海量的图片库中找到和待识别图片最相近的图片。这种操作在以前依靠图片名搜图的时代是难以想象的,直到出现了CBIR(Content-based image ...
  • png、jpg、gif三种图片格式的区别

    万次阅读 2017-12-08 10:05:10
    为什么想整理这方面的类容,我觉得就像油画要了解他的颜料和画布、雕塑要了解他的石材一样,作为网页设计师也应该对图片格式的特性有一定了解,这样才能更好的表达你的创意和想法。 除此之外,我们在平时工作中...
  • JQuery实现图片3D旋转,轮播

    千次阅读 2020-01-14 21:34:36
    JQuery实现图片3D旋转 X轴正方向向右、Y轴正方向向下、Z轴正方向从屏幕指向自己 js代码: ```javascript <script> window.onload=function ( ) { var element=document.getElementById('box');//...
  • 原有页面是展示一堆图片,这些图片从后台获取的,后台直接往outputStream输出图片的二进制数据,前端通过img标签的src属性来显示这些图片 现在由于增加了鉴权功能,使用的jwt,往http header里面放jwt来鉴权的,然后...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,068
精华内容 24,027
关键字:

代表家的图片