Select to view content in your preferred language

Optimizing Textures for Esri 3D Software

1171
3
11-19-2018 05:46 AM
by Anonymous User
Not applicable
1 3 1,171

Most people working with 3D modeling software are not aware of how heavy textures can impact performance in the modeling process and the end-user experience.

When generating textures I always recommend creating them at a higher resolution than the final product. The reasoning is that you can always down-sample the image (Reduce number of pixels) later on but you can never scale the image back up.

For procedural modeling in Esri CityEngine and generating your libraries optimizing texture and model asset size as much as possible is key for ensuring fast performance and the ability to model massive environments.

The following script takes the difficulty out of manually scaling the images in your pipeline enabling complete automation allowing you to continue focusing on the design aspect of your project you know and love...

# -------------------------------------------------------------------------------
# Name:        ImageAutoResize.py
# Purpose:     Script for batch optimizing image for optimal performance in 3D software
#
# Author:      Geoff Taylor
#
# Created:     11/19/2018
# Copyright:   (c) Esri 2018
# updated:

# Required:
# License: Apache

# -------------------------------------------------------------------------------

import os, sys
from math import ceil
from PIL import Image, ImageStat


inFolder = r'inputFolderPathGoesHere!'
outFolder = r'outputFolderPathGoesHere!'

''' Max Width and height are only relative for a single side of the image. 
if one side of the image is larger then the smaller side will be scaled to the minimum value 
and larger scaled fractionally to the smaller side'''

minWidth = 300
minHeight = 300

fileFormats = ['.jpg'# Currently only Supports .jpg

###############
# Definitions #
###############


def update_progress(progress):
    barLength = 10  # Modify this to change the length of the progress bar
    status = ""
    if isinstance(progress, int):
        progress = float(progress)
    if not isinstance(progress, float):
        progress = 0
        status = "error: progress var must be float\r\n"
    if progress < 0:
        progress = 0
        status = "Halt...\r\n"
    if progress >= 1:
        progress = 1
        status = "Done...\r\n"
    block = int(round(barLength*progress))
    text = "\rPercent Complete: [{0}] {1}% {2}".format("#"*block + "-"*(barLength-block), progress*100, status)
    sys.stdout.write(text)
    sys.stdout.flush()


def fileExt(inFile):
    ''' [0] returns fileName, [1] returns Extension '''
    filename, file_extension = os.path.splitext(inFile)
    return filename, file_extension.lower()


def genDirectory(inPath):
    if not os.path.exists(inPath):
        os.makedirs(inPath)


def delIfFileExists(inFile,outFolder):
    fullFilePath = os.path.join(outFolder, inFile)
    if os.path.exists(fullFilePath):
        try:
            os.remove(fullFilePath)
        except:
            print("Could not remove {0}".format(fullFilePath))
    else:
        pass


def imageColorType(pilImg):
    thumb_size = 40
    MSE_cutoff = 22
    adjust_color_bias = True
    bands = pilImg.getbands()
    if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
        thumb = pilImg.resize((thumb_size, thumb_size))
        SSE, bias = 0, [0, 0, 0]
        if adjust_color_bias:
            bias = ImageStat.Stat(thumb).mean[:3]
            bias = [b - sum(bias)/3 for b in bias]
        for pixel in thumb.getdata():
            mu = sum(pixel)/3
            SSE += sum((pixel - mu - bias)*(pixel - mu - bias) for i in [0, 1, 2])
        MSE = float(SSE)/(thumb_size*thumb_size)
        if MSE <= MSE_cutoff:
            return "grayscale"
        else:
            return "color"
    elif len(bands) == 1:
        return "bw"
    else:
        return "unknown"


def imageFitsDimensions(inPilImg, minWidth, minHeight):
    width, height = inPilImg.size
    if width <= minWidth or height < minHeight:
        return True
    else:
        return False


def imageScaling(inPilImg, minWidth, minHeight):
    width, height = inPilImg.size
    if height > width:
        scaleFactor = minWidth / width
        return minWidth, ceil(scaleFactor*height)
    elif width > height:
        scaleFactor = minHeight / height
        return ceil(scaleFactor*width), minHeight
    else:
        return minWidth, minHeight


################
# Begin Script #
################

genDirectory(outFolder)

imageFiles = [f for f in os.listdir(inFolder) if
              os.path.isfile(os.path.join(inFolder, f)) and fileExt(f)[1] in fileFormats]

count = 0
for imageFile in imageFiles:
    if count > 0:
        update_progress(count/len(imageFiles))
    delIfFileExists(imageFile, outFolder)
    inFile = os.path.join(inFolder, imageFile)
    outFile = os.path.join(outFolder, imageFile)
    count += 1
    try:
        im = Image.open(inFile)
        if imageColorType(im) != "color":
            im.convert('RGB')
        if imageFitsDimensions(im, minWidth, minHeight):
            im.save(outFile, "JPEG")
        else:
            imageScaling(im, minWidth, minHeight)
            im.save(outFile, "JPEG")
            outImg = im.resize((imageScaling(im, minWidth, minHeight)[0], imageScaling(im, minWidth, minHeight)[1]),
                               Image.ANTIALIAS)
            # outImg.save(outFile, quality=95)
            outImg.save(outFile, optimize=True, quality=95)
    except IOError:
        print("cannot resample image for" % imageFile)
update_progress(100)

Just install the Python Imaging Library fork "Pillow" https://python-pillow.org/ and run the script on the folder containing your textures.

3 Comments