Skip to content

Instantly share code, notes, and snippets.

@yin8086
Last active November 18, 2023 03:31
Show Gist options
  • Save yin8086/e9d1c624a113d2478ac0 to your computer and use it in GitHub Desktop.
Save yin8086/e9d1c624a113d2478ac0 to your computer and use it in GitHub Desktop.
arm astc(Adaptive Scalable Texture Compression, Unity asset) to png, and png to astc, use official `astcenc.exe`
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 15 15:53:10 2015
@author: Stardrad
astcenc.exe: https://www.dropbox.com/s/u5w6d972nniursu/ASTC-Evaluation-Codec-1.3.zip?dl=0
"""
import os, struct, fnmatch, time, math
from PIL import Image
def walk(adr):
#root,dirs,files = os.walk(adr).next()
for root,dirs, files in os.walk(adr):
for name in files:
if fnmatch.fnmatch(name, '*.tex') :
yield os.path.join(root, name)
def get_compress_dim(target_bitrate):
blockdims = [4, 5, 6, 8, 10, 12]
best_error = 1000
aspect_of_best = 1
tar_compress = [0, 0]
for i in xrange(6):
for j in xrange(i, 6):
is_legal = (j==i) or (j==i+1) or (j==3 and j==1) or (j==4 and j==1) or (j==4 and j==2)
if is_legal:
bitrate = 128.0 / (blockdims[i] * blockdims[j])
bitrate_error = math.fabs(bitrate - target_bitrate)
aspect = float(blockdims[j]) / blockdims[i]
if (bitrate_error < best_error) or (bitrate_error == best_error and aspect < aspect_of_best):
tar_compress[0] = blockdims[i]
tar_compress[1] = blockdims[j]
best_error = bitrate_error
aspect_of_best = aspect
return tar_compress
if __name__ == '__main__':
ntime = time.time()
mode_str = 'RGB'
for curName in walk(ur'.'):
ind = 0
fName = curName[curName.rindex('\\') + 1:]
print 'Process %s' % fName
with open(curName,'rb') as fUnityIn:
print '....',
# 1. Get Basic Unity3d File Info
fUnityIn.seek(0)
fNameLen, = struct.unpack('<I', fUnityIn.read(4))
curPos = fUnityIn.tell()
curPos += fNameLen
curPos = (curPos + 4 - 1) / 4 * 4
fUnityIn.seek(curPos)
width, height, = struct.unpack('<II', fUnityIn.read(8))
dataSize, = struct.unpack('<I', fUnityIn.read(4))
unityType, = struct.unpack('<I', fUnityIn.read(4))
tarBpp = float(dataSize * 8) / ( width * height)
best_compress = get_compress_dim(tarBpp)
# 2. Check Unity3d ASTC Format
ASTC_TYPE_DISTANCE = 6
BASIC_RGBA_TYPE = 0x36
if unityType < BASIC_RGBA_TYPE:
mode_str = 'RGB'
else:
mode_str = 'RGBA'
if unityType < 0x30 or unityType > 0x3B:
print 'Not ASTC mode, format=%04x' % unityType
continue
elif unityType == 0x30 or unityType == 0x30 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 4 and best_compress[1] == 4:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
elif unityType == 0x31 or unityType == 0x31 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 5 and best_compress[1] == 5:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
elif unityType == 0x32 or unityType == 0x32 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 6 and best_compress[1] == 6:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
elif unityType == 0x33 or unityType == 0x33 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 8 and best_compress[1] == 8:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
elif unityType == 0x34 or unityType == 0x34 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 10 and best_compress[1] == 10:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
elif unityType == 0x35 or unityType == 0x35 + ASTC_TYPE_DISTANCE:
if best_compress[0] == 12 and best_compress[1] == 12:
print 'ASTC %dx%d %s mode' % (best_compress[0], best_compress[1], mode_str)
else:
print 'Format(%08x) with (%d, %d)' % (best_compress[0], best_compress[1])
# 3. Read ASTC raw data
originalPos = fUnityIn.tell()
fUnityIn.seek(0,2)
fileSize = fUnityIn.tell()
fUnityIn.seek(originalPos + 0x4 * 10)
dataSize_2, = struct.unpack('<I', fUnityIn.read(4))
if dataSize_2 == dataSize:
fUnityIn.seek(originalPos + 0x4 * 10 + 4)
else:
fUnityIn.seek(fileSize - dataSize)
x_blocks = (width + best_compress[0] - 1) / best_compress[0]
y_blocks = (height + best_compress[1] - 1) / best_compress[1]
astcSize = (x_blocks * y_blocks) << 4;
assert astcSize == dataSize, 'File Size Error'
astcData = fUnityIn.read(dataSize)
# 4. Create ASTC File
with open(curName + '._astc','wb') as fASTcOut:
fASTcOut.write('\x13\xab\xa1\x5c')
fASTcOut.write(struct.pack('BBB', best_compress[0],best_compress[1],1))
widthByte = struct.pack('<I', width)[:3]
heightByte = struct.pack('<I', height)[:3]
fASTcOut.write(widthByte)
fASTcOut.write(heightByte)
fASTcOut.write(struct.pack('<I', 1)[:3])
fASTcOut.write(astcData)
args = 'astcenc.exe -d %s %s -silentmode' % ( curName + '._astc', curName + '.tga' )
os.system(args)
os.remove(curName + '._astc')
im = Image.open( curName + '.tga' ).convert('RGBA')
imageName = ('%s.%s.%dx%d.%s.png') % (curName, 'astc', best_compress[0], best_compress[1], mode_str)
showImName = ('%s.%s.%dx%d.%s.png') % (fName, 'astc', best_compress[0], best_compress[1], mode_str)
im.save(imageName)
os.remove(curName + '.tga')
print 'save to %s' % showImName
print 'Total time: %lf' % (time.time() - ntime)
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 15 16:11:02 2015
@author: Stardrad
astcenc.exe: https://www.dropbox.com/s/u5w6d972nniursu/ASTC-Evaluation-Codec-1.3.zip?dl=0
"""
import os, struct, fnmatch, time
from PIL import Image
def walk(adr):
#root,dirs,files = os.walk(adr).next()
for root,dirs, files in os.walk(adr):
for name in files:
if fnmatch.fnmatch(name, '*.png') :
yield os.path.join(root, name)
if __name__ == '__main__':
ntime = time.time()
for curName in walk(ur'.'):
ind = 0
fName = curName[curName.rindex('\\') + 1:]
print 'Process %s' % fName
print '....',
pos_end = curName.rfind('astc')
point_end = curName.rfind('.')
rgb_end = curName.rfind('RGB')
if pos_end == -1 or point_end == -1 or rgb_end == -1:
print 'Pass'
continue
# 1. Read png file and basic infos
compressType = curName[pos_end + 5: rgb_end - 1]
print 'compress type:%s' % compressType
rgb_type = curName[rgb_end : point_end]
print 'RGB Type:%s' % rgb_type
orig_fName = curName[:pos_end-1]
im = Image.open( curName ).convert('RGBA')
png_width,png_height, = im.size
if rgb_type == 'RGB':
esw_param = '-esw rgb1'
else:
esw_param = '-esw rgba'
# 2. Compress to astc format
args = 'astcenc.exe -c %s %s %s -medium -silentmode %s' % \
( curName, orig_fName + '._astc' ,\
compressType, esw_param)
os.system(args)
with open(orig_fName + '._astc','rb') as fASTcIn:
fASTcIn.seek(0x10)
astcData = fASTcIn.read()
os.remove(orig_fName + '._astc')
with open(orig_fName,'rb+') as fUnityIn:
fUnityIn.seek(0)
fNameLen, = struct.unpack('<I', fUnityIn.read(4))
curPos = fUnityIn.tell()
curPos += fNameLen
curPos = (curPos + 4 - 1) / 4 * 4
fUnityIn.seek(curPos)
width, height, = struct.unpack('<II', fUnityIn.read(8))
if png_width != width or png_height != height:
print 'Pass , size not match'
continue
sizePos = fUnityIn.tell()
dataSize, = struct.unpack('<I', fUnityIn.read(4))
unityType, = struct.unpack('<I', fUnityIn.read(4))
if unityType < 0x30 or unityType > 0x3B:
print 'Not ASTC mode, format=%04x' % unityType
continue
originalPos = fUnityIn.tell()
fUnityIn.seek(0,2)
fileSize = fUnityIn.tell()
fUnityIn.seek(originalPos + 0x4 * 10)
dataSize_2, = struct.unpack('<I', fUnityIn.read(4))
tarBpp = float(dataSize * 8) / ( width * height)
if dataSize_2 == dataSize:
fUnityIn.seek(originalPos + 0x4 * 10 + 4)
else:
fUnityIn.seek(fileSize - dataSize)
assert dataSize == len(astcData), 'Input size not satisfied'
# We do not support modify file size
# once the compress type is got, file size is not changed.
# sizePos2 = fUnityIn.tell() - 4
# fUnityIn.seek(sizePos)
# fUnityIn.write(struct.pack('<I', len(astcData)))
# fUnityIn.seek(sizePos2)
# fUnityIn.write(struct.pack('<I', len(astcData)))
fUnityIn.write(astcData)
print 'import to %s' % orig_fName
print 'Total time: %lf' % (time.time() - ntime)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment