# A utility command for displaying an histogram of the current image.
# Colored images are internally converted to an illuminace grayscale
# before the IceMan.Histogram() operator generates the data used to
# draw a bar graph. The shape of the graph displayed in 'it' will
# match a PhotoShop histogram generated by the,
# Image->Adjustments->Levels menu
# Unlike PhotoShop this utility makes no attempt to apply differential
# vertical scaling in order to avoid excessively "flat" bar graphs.
#
# M.Kesson Aug 25 2014
# Feb 19 2016: Added html output.
import math,it,ice,os,time
import struct
from it.It3Command import It3Command
class Histogram(It3Command):
def __init__(self):
It3Command.__init__(self)
self.m_menuPath = 'Commands/Histogram'
def Invoke(self):
self.histogram()
def histogram(self):
try:
elem = it.GetCurrentElement()
except:
it.app.Warning('The Histogram command cannot be used without an image.')
it.app.RaiseLogWindow()
return
srcImage = elem.GetImage()
# Histogram graphic____________________________________
w,h = self.getDimensions(srcImage)
h_totalPixels = w * h
# Light gray background
bg_image = ice.FilledImage(ice.constants.FLOAT,[0,512,0,256],[0.9,0.9,0.9])
# Dark gray bars
bar_color = ice.Card(ice.constants.FLOAT, [0.24,0.24,0.24,1])
bar_width = 2
# Histogram values_____________________________________
data = srcImage.Histogram(256, [0,1])
gray = []
max_bar_num = 0
num_components = len(data)
if num_components > 3:
num_components = 3
for n in range(len(data[0])):
if num_components == 3:
num = data[0][n] * 0.21 + data[1][n] * 0.72 + data[2][n] * 0.07
else:
num = data[0][n]
gray.append(num)
if num > max_bar_num:
max_bar_num = num
# Bar Chart Outline___________________________________
verts = [(0,0)]
for n in range(len(gray)):
# Scaling by 220 pixels ensures the tallest bar will not touch
# the top edge of the background.
y = (float(gray[n])/max_bar_num) * 220
x = n * bar_width
verts.append( (x,y) )
verts.append( (x + 1,y) )
verts.append( (512,0) )
bars = ice.PolyFill(bar_color, verts, [0,0]) # zero filtering
# Compositing________________________________________
bg_image = bg_image.Over(bars)
bg_image = bg_image.Flip(False, True, False) # flip vertically
it.AddImage(bg_image)
path = self.makeOutDirectory()
imagepath = os.path.join(path, 'src_rgb.jpg')
self.saveImage(srcImage, imagepath, 2.2)
#hsv_image = srcImage.RGBToHSV()
gray = srcImage.RGBToHSV().Shuffle([2])
imagepath = os.path.join(path, 'src_gray.jpg')
self.saveImage(gray, imagepath, 2.2)
htmlpath = os.path.join(path, 'histogram.html')
it.app.Info('Saving histogram.html "%s"' % htmlpath)
it.app.RaiseLogWindow()
self.saveHtml(htmlpath, verts)
#-----------------------------------------------------
# Returns width and height
def getDimensions(self, image):
box = image.DataBox()
return [box[1] - box[0] + 1, box[3] - box[2] + 1]
def saveImage(self, image, path, gamma):
image = image.Gamma(gamma);
image.SetMetaDataItem('JPEG_QUALITY', 100)
image.Save(path, ice.constants.FMT_JPEG)
def makeOutDirectory(self):
outname = 'histogram'
cwdpath = os.getcwd()
if len(cwdpath) < 3:
cwdpath = os.environ['HOME']
out_dirpath = os.path.join(cwdpath, outname)
if not os.path.exists(out_dirpath):
os.mkdir(out_dirpath)
return out_dirpath
def saveHtml(self, path, verts):
f = open(path, 'w')
f.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"\n')
f.write(' "http://www.w3.org/TR/REC-html40/loose.dtd">\n')
f.write('<HTML><HEAD><TITLE>Histogram</TITLE></HEAD>\n')
f.write('<BODY BGCOLOR="#ffffff">\n')
f.write('<BR><BR><BR><BR><BR><BR><BR>\n')
f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
f.write(' <TR>\n')
f.write(' <TD width="50" NOWRAP> </TD>\n')
f.write(' <TD NOWRAP>\n')
f.write(' <img src="src_rgb.jpg" onmouseover="this.src=\'src_gray.jpg\'" onmouseout="this.src=\'src_rgb.jpg\'" alt="" width=520 border="0">\n')
f.write(' <BR><BR><BR>\n')
f.write(' </TD>\n')
f.write(' </TR>\n')
f.write('</TABLE>\n')
f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
f.write(' <TR>\n')
f.write(' <TD width="50" NOWRAP> </TD>\n')
emptyBinCount = 0.0;
nonEmptybinCount = 0.0
for n in range(0,len(verts),4):
hexstr = self.getHexStr( float(n)/ (len(verts) * 1.1))
try:
vert_1 = verts[n]
vert_2 = verts[n+1]
vert_3 = verts[n+2]
vert_4 = verts[n+3]
y = (vert_1[1] + vert_2[1] + vert_3[1] + vert_4[1])/4
except:
y = 0
if y == 0:
emptyBinCount += 1.0
hexstr = "ff0000"
else:
nonEmptybinCount += 1.0
if y < 2:
y = 2
f.write(' <TD valign="bottom" NOWRAP>\n')
f.write(' <div style="width:2px;height:%dpx;background-color:#%s"></div>\n' % (y,hexstr))
f.write(' </TD>\n')
dynamicRangeNotUsed = emptyBinCount / (emptyBinCount + nonEmptybinCount)
f.write(' </TR>\n')
f.write('</TABLE>\n')
f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
f.write(' <TR>\n')
f.write(' <TD width="50" NOWRAP> </TD>\n')
f.write(' <TD NOWRAP>\n')
f.write(' <BR><P>Dynamic range <B>not</B> used: %1.1f percent.</P>\n' % (dynamicRangeNotUsed * 100) )
f.write(' </TD>\n')
f.write(' </TR>\n')
f.write('</TABLE>\n')
f.write('</BODY></HTML>\n')
f.close()
def getHexStr(self, percent):
val = int(16 * percent)
return hex(val).split('x')[1] * 6
# Add the new menu item
it.commands.append(Histogram)