πŸ“œ ⬆️ ⬇️

To the issue of cross-browser Data URI

In pursuit of site optimization, I wanted to reduce the number of requests, not to the detriment of the size of the optimized files.
The goal is to transfer images in different formats in one file, with different optimization settings.
As a tool, I chose data uri and gzipped css file. However, IE with data uri work very badly. But they have mhtml. The existing implementation did not meet my requirements, since I had to transfer 1 file twice - once for IE, in mhtml, and the second for all others, in data uri. In search of a solution, I came across an article from bolk , which described a solution for the jpeg format and some theoretical calculations for gif and png. After almost three weeks of maneuvering, I managed to implement a solution for gif and png and automate the process for all three formats.

BASE64


Since the images are transmitted to base64, it is worth highlighting some moments related to this encoding.


Information for consideration:
Base64 in English wikipedia


')

Jpeg


JPEG section format: [header] [data]

C jpeg is simple and described in bolk 'a:
Open the HEX editor:
FF D8 - JPEG Header for IE
FF E0 - the announcement of the section APP0, where everything is hidden before the image data,
β€œ ; Background-color: url (data: image / jpeg; base64, ” this is what other browsers see.
When IE decodes this string, it turns out trash that does not affect anything
FF D8 - the beginning of JPEG for other browsers
" Image data " is already seen by all browsers.


The point is, what would a line in CSS look like:



And decrypted in IE as:



And others saw her as:



Because of the base64 features, it is necessary to additionally transmit a certain number of characters, so that the string is encrypted \ correctly decrypted. They are inserted before and after CSS. The number was calculated and selected empirically:
/ 9j / 4AA0; background-image: url (data: image / jpeg; base64; 00,

My script that automates this process is:
#!/usr/bin/ruby
require 'base64'
# :
a= "/9j/4AA0;background-image:url(data:image/jpeg;base64;00,"

#
b=Base64.encode64( File .open( "#{ARGV[0]}" , 'r' ){|f| f.read})

#
File .open( 'temp' , 'w' ){|i| i.write( "#{Base64.decode64(a)}#{Base64.decode64(b)}" )}

# base64
#cat test | base64 | tr -d "\n" > jpeg64.txt
File .open( 'temp2' , 'w' ){|o| o.write(Base64.encode64( File .open( 'temp' , 'r' ){|f| f.read}))}
# File .delete( 'temp' )

c= File .open( 'temp2' , 'r' ){|f| f.read}.gsub(/\/9j\/4AA0backgroundimageurldataimage\/jpegbase6400/, "/9j/4AA0;background-image:url(data:image/jpeg;base64;00," ).gsub(/\n/, "" )

File .open( 'out_jpeg64' , 'w' ){|s| s.write( "#{c}\);" )}
File .delete( 'temp2' )
# css
# cat output64 | tr -d "\n"
# mhtml!!!


* This source code was highlighted with Source Code Highlighter .


Information for consideration:
JPEG in English wikipedia



Gif


Things are not so good with this format.

What can be done:


I chose the second option, this makes the base64 line more readable and allows you to convert any non-animated gif.

More or less standard version of GIF sections: [header] [size] [data] [00]

Many GIFs do not have the correct field order. For example, if you make `convert jpeg gif` then the resulting file will not be adequately processed by the script. Use GIMP.
The first 13 bytes is the one that cannot be reduced. Moreover, 11 bytes is complex and complex and describes the Global Color Table. We change it to 00
Cut the color table (from 14 bytes to 21 FE xx, where xx is the size of the comment)
Comment with css and first 13 bytes.
Cut the color table (from 14 bytes to 21 FE xx, where xx is the size of the comment)
'Internal comment' 1 character long
Cut the color table (from 14 bytes to 21 FE xx, where xx is the size of the comment)
2c 00 00 00 00 - Image descriptor. Its 10th byte is complexly complex and describes the Local Color Table. We transfer from the 11th byte everything that is transferred (declare Local Color Table, sorted \ no, the size of the Local color table), more in the format specification.
Insert color table
Continued Image descriptor


The point is, what would a line in CSS look like:



While before all edits, the file looked like:



My script to automate the process:
#!/usr/bin/ruby
# CONVERT INCORRECTLY TRANSFER DATA. USE GIMP INSTEAD
# USE: ./GIF_SCRIPT.RB [GIF_FILE]
require 'base64'

# OPEN GIF FILE IN HEX
orig= File .open( "#{ARGV[0]}" , 'r' ){|f| f.read.unpack( "H*" )}.to_s

# FUTURE HEADER
header=orig[0..25]

# GREP GENERAL COLOR TABLE
# [26..1565]/6 = 256 BYTE (MAX SIZE OF COLOR TABLE)
color_table=orig[26..1565][/(.*)21fe/,1]
if color_table. class == NilClass
color_table=orig[26..1575][/(.*?)2c0000/,1]
end

# FOR DEBUGING
#puts color_table
#puts color_table.length
puts "COLORS IN PALLETE: #{color_table.length/6}"

# GIF IMAGE DATA
data=orig[/2c0000.*/]

# SAVE 11 BYTE 'S INFO AND ADOPT IT FOR LOCAL COLOR TABLE
eleven=header[20..21].to_i(16).to_s(2)
local_mix="10#{eleven.split("")[4].to_s}00#{eleven.split("")[5..7].to_s}".to_i(2).to_s(16)

# 11 BYTE TO ZERO
header[20..21]="00"
# DECLARE LOCAL COLOR TABLE
data[18..19]=local_mix

# MAGIC COMMENT
comment=Base64.decode64(";background-image:url(data:image/gif;base64;pzd,").unpack("H*").to_s

# WRITE ALL IN ONE FILE
var=header+"21fe313030"+comment+header+"21fe013000"+data[0..19]+color_table+data[20..-1]
File.open('
out .gif ',' w '){|f| f.write(var.to_a.pack("H*"))}

# ENCODE FILE TO BASE64 WITH "\n" REMOVING
File.open('
temp ',' w '){|o| o.write(Base64.encode64(File.open(' out .gif ',' r '){|f| f.read}).gsub(/\n/,""))}

# MAKE STRING CSS READEABLE
c=File.open('
temp ',' r '){|f| f.read}.gsub(/backgroundimageurldataimage\/gifbase64pzd/,";background-image:url(data:image/gif;base64;pzd,").gsub(/\n/,"")
File.delete('
temp ')

# JUST PASTE TEXT FROM THIS FILE TO CSS
File.open('
out_gif64 ',' w'){|s| s.write( "#{c}\);" )}


* This source code was highlighted with Source Code Highlighter .



There is no script for animated gif. I think it's better to use animated CSS sprites.

Theoretical calculations:



Information for consideration:
Gif color tables
Gif specification



PNG


After gif is a safe haven. The sections have unlimited size, they have 4 byte headers and it is very convenient to look for them. For comparison, for gif I was puzzled and debugged the script almost all day, and for png I did everything in an hour.

Section format in PNG: [size (4 bytes)] [data] [CRC (4 bytes)]

And here it was not without pitfalls. CRC is very important for IE, if the CRC is broken then IE will not display a picture. All the rest is deeply parallel, beaten or not.

Many PNGs do not have a completely correct structure, in any case, my script will not work with us until it is driven through optipng . In addition to optimizing the image, this program will put the fields in the correct order. Also, I have noticed that Photoshop sometimes cuts sRGB fields and it doesn’t always handle saved pngs.

CSS will hide in the tEXt section

PNG should be immediately optimized using optipng, then cut into such a way that tExt would be immediately behind the IHDR.
In the tEXt section, keyword00 must be transmitted, its length is taken into account in the total section length. I have this 'Comment'

General order:
Ihdr
tExt
Other service information
data



It was:



It became:



The script is well commented, and much can be learned in the specification.

IE6 does not see transparency, sometimes it can be fixed with the help of bKGD exposing the desired Background color.

Then we run `optipng -fix FILE` to fix the CRC of the tEXt section

My script to automate the process:
#!/usr/bin/ruby
#
#!!!! RUN optipng FIRST !!!!
#
# USE: ./PNG_SCRIPT.RB [PNG_FILE]
require 'base64'
# OPEN GIF FILE IN HEX
orig= File .open( "#{ARGV[0]}" , 'r' ){|f| f.read.unpack( "H*" )}.to_s

#ihdr=orig[0..65]
ihdr=orig[/(.*?)73524742/,1][0..-9]

#sRGB - 73 52 47 42 & -4b (8 characters)
#srgb_phys=orig[66..171]
#check for tEXt existence
if orig[/74455874/]. class == NilClass
srgb_phys=orig[/(.{8}73524742.*?)49444154/,1][0..-9]
else
srgb_phys=orig[/(.{8}73524742.*?)74455874/,1][0..-9]
end

#srgb_phys=orig[/(.{8}73524742.*?)74455874/,1][0..-9]

#tEXt - 74 45 58 74 β€“Ρšβ€“ΠŠβ€”β€“Ρ—β€“Β΅β€“Ρ–β€“Ρ™β€“Π„β€“Β΅ 8 β€“Ρ™β€“βˆžβ€“Ρ–β€“ΠŠ –Љ–¡–љ——— β€“Ρ™β€“βˆž CRC 00000000
#text=orig[172..245]
#text=orig[/(.{8}74455874.*?)49444154/,1][0..-9]

#IDAT - 49444154
#data=orig[246..-1]
data=orig[/.{8}49444154.*/]

#MAGIC COMMENT
comment=Base64.decode64( ";background-image:url(data:image/png;base64;pzd," ).unpack( "H*" ).to_s

###### OUTER PNG
# "00000059" + "74455874" + "436f6d6d656e7400"
# tEXt_length + 'tEXt' + 'Comment.'
# "3030" - two zero for base64 balance
###### INNER PNG
# "00000008" + "74455874" + "436f6d6d656e7400" + "00000000"
# min_tEXt_length + 'tEXt' + 'Comment.' + blank CRC
#
# CRC field one for two PNG 's
# IE can'
t live without it, but others feel indifferently
var =ihdr+ "00000059" + "74455874" + "436f6d6d656e7400" + "3030" +comment+ihdr+ "00000008" + "74455874" + "436f6d6d656e7400" + "00000000" +srgb_phys+data

File .open( 'out.png' , 'w' ){|f| f.write( var .to_a.pack( "H*" ))}

# CRC FIX
puts "optipng -fix started..."
`optipng -fix out .png`
puts "optipng -fix completed"

# ENCODE FILE TO BASE64 WITH "\n" REMOVING
File .open( 'temp' , 'w' ){|o| o.write(Base64.encode64( File .open( 'out.png' , 'r' ){|f| f.read}).gsub(/\n/, "" ))}

# MAKE STRING CSS READEABLE
c= File .open( 'temp' , 'r' ){|f| f.read}.gsub(/backgroundimageurldataimage\/pngbase64pzd/, ";background-image:url(data:image/png;base64;pzd," ).gsub(/\n/, "" )
File .delete( 'temp' )

# JUST PASTE TEXT FROM THIS FILE TO CSS
File .open( 'out_png64' , 'w' ){|s| s.write( "#{c}\);" )}


* This source code was highlighted with Source Code Highlighter .


Information for consideration:
PNG basics
PNG Specification



Mhtml


If MHTML is used, the CSS should be all edited for it and divided into sections (example in the archive):
/*
Content-Type: multipart/related; boundary="_"

--_
Content-Type: text/css;

*/
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}

#half_logo {
/*
--_
Content-Location:logo
Content-Transfer-Encoding:base64
Content-Type: image/png;*/

iVBORw0KGgoAAAANSUhEUgAAAT4AAAA3CAMAAACintZ+AAAAWXRFWHRDb21tZW50ADAw;background-image:url(data:image/png;base64;pzd,iVBORw0K...);

/*
--_
Content-Type: text/css;

*/
background-image: url(mhtml:http://192.168.1.2/test.css!logo) !ie;
/*
--_--
*/

* This source code was highlighted with Source Code Highlighter .


Archive with source codes and scripts
Sample working site

Tested in FF 3.6, Opera 10.10, chromium, chrome, IE6-8

PS: The author of this article is my best friend Banderlog. I place the article at his request, accordingly I recommend asking questions directly to him in jabber: banderlog@jabber.com.ua
PPS: It is strange that only the second day revealed the fact that when posting an article, an error was made in the scripts. All 3 were the same.

Source: https://habr.com/ru/post/90761/


All Articles