-- @author Mark Bassett -- @version 0.1 -- Last modified 09/20/2023 -- @shortcut ctrl + L -- Turn this information into Help accessible from the UI local strHelpText = string.format( [[ Luminance Analysis of Images. Purpose. The objective of this script is to help a designer determine the light fixtures they should use in a space. The space can be both artifically and naturally illuminated so that prescribed functional tasks can be comfortably performed, and various aesthetic requirements are met. To do this they need to measure, ahead of time, the amount and color of light reflected by various surfaces of interest in a proposed design. Results are presented overlayed upon the current render target result or, a greyscale image of luminance values or, as a False Color Thematic Map of Luminance values. ********************************************************************************************************** **** The Theory ********************************************************************************************************** Assumptions. A rendering application calculates the color and intensity of the light reflected by a surface to paint a pixel in the plane of the view that the reflected light happens to pass through on the way to the observer's eye. However, this calculation is not what is normally displayed to the observer. The calculated color and intensity is a component of the information supplied to a monitor to tell it how to configure the pixel for the observer. In order to better represent reality to a human observer, the color and intensity are mathematically manipulated to align with characteristics of human visual perception, the monitor, and efficient storage of the data in a process referred to as Tone Mapping. If we can access the color and intensity data prior to any mathematical manipulation, we should be able to extract some information useful to the lighting designer's needs described above. Methodology. (Or how do we do this utilizing the capabilities in Octane? Otoy, please critique this) In Octane, the raw color and intensity data is only visible when tone mapping is removed, and other settings are correct. To visualize this information, the response curve should be set to Linear/off, and the Gamma to 1. Other settings also need to be neutralized. Exposure should be set to 1. White light spectrum should be set to D65. Kernal to path tracing. (Others?) The equation for calculating the amount of light reflected by a surface using its RGB channel information is 179 * (0.265*R + 0.670*G + 0.065*B) lm/w, where R,G & B are linear, linear meaning normalized (0-1), gamma free RGB values. See this web page for additional information: This is the luminance value of the original diffuse Map calculated per LBNL Radiance software. https://discourse.radiance-online.org/t/luminous-efficacy/1400 If we make the diffuse image's RGB values linear, i.e. normalize the sRGB values beforehand, by dividing them all by their possible maximum value of 255 and then converting them back to sRGB the equation produces a theoretical maximum of 45645 lm/w. -- What is stored in the luminance image. ((0.265 * lR) + (0.670 * lG) + (0.065 * lB)) * 255 -- What is reported in measurements where lR,lG, lB are linear (normalized, gamma free RGB) color channel values. 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255 (Again can someone please confirm this, I cannot find any mention of these inputs needing to be linear when this equation is discussed, however there is plenty of discussion around similar equations used to mathematically manipulate sRGB pixels and how they should be made linear beforehand, essentially manipulate in linear color space, but display in sRGB color space). Currently the script determines the Luminance using pixels resulting from a Render Target setup as follows. Kernal.Type = Path Tracing Kernal.White light spectrum = D65 Imager.Exposure = 1 Imager.Vignetting = 0 Imager.White point = 1,1,1 Imager.Saturation = 1 Imager.Response curve = sRGB Imager.Gamma = 1 It samples the pixels in the Beauty Pass to produce an effectively untonemapped greyscale image that it calculates internally by removing the Gamma (an sRGB with a gamma of 1 is equivalent to a linear/off image with a gamma of 2.2, by removing the Gamma we have internally the same thing as Octane's linear/off version of the beauty pass that we can then use to calculate the Luminance of each pixel). Step 1, Normalize and Remove the Gamma. For each pixel get the 3 channel values (sR,sG,sB) from the Beauty Pass and feed them to the following function. The resulting gamma free normalized color component (nC) in the range of 0-1, equals the result of this function applied to the sRGB input component (sC) in the range 0-255. nC = ((sC/255) <= 0.04045 and (sC/255)/12.92 or math.pow(((sC/255)+0.055)/1.055, 2.4)) Step 2, Display the 'Luminance' image. For each normalized channel value above, include it in the following equation and multiply the result by 255 to get it back to sRGB so it can be displayed in an Octane LDR_MONO, or an Octane LDR_RGBA image type. greyLumens = (0.265*lR + 0.670*lG + 0.065*lB) * 255, where lR, lG, and lB are linear (normalized, gamma free RGB) values. This result can be used directly to set the pixel value in a greyscale/monochrome image type and displayed on screen, or it can be converted to a 4 component vector for use with an sRGBA image of type Octane LDR_RGBA as follows. clrLumens = {greyLumens ,greyLumens ,greyLumens ,255},(looks grey on screen but can have color themes applied). This image is still Gamma free and should be visually equivalent to an Octane image with the response curve set to Linear/off and Saturation set to 0. When you look at the image on the Luminance Image Tab in the application, each pixel represents this number. Step 3, Report the Luminance values to the user. Sample the pixels in the Luminance image, if it is a greyscale image, multiply the sample value by 179 to get the value in lux. If it is a color image, multiply any one of the channel values in the sample by 179 to get the value in lux, thus any measurements reported by the application are the result of this equation 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255, where lR,lG, and lB are linear (normalized, gamma free RGB) values. Alternatively, sample the pixels in a beauty pass and feed it through the whole process as follows local Lux = 179 * (0.265*inv_Gamm(sR) + 0.670*inv_Gamma(sG) + 0.065*inv_Gamma(sB)) * 255, where sR, sG, and sB are the diffuse image sRGB channel values in the range 0-255. ********************************************************************************************************** **** Could someone please verify that my logic here is correct. ********************************************************************************************************** Installation: Copy the script to Octane's default script location spacified under Rreferences > Application > Script Directory: Within Octane, execute Script > Rescan script folder. It should appear as Image Luminance Analysis under the script dropdown menu. Setting up the Scene: Setup the Render Target and Imager as follows: Kernal.Type = Path Tracing Kernal.White light spectrum = D65 Imager.Exposure = 1 Imager.Vignetting = 0 Imager.White point = 1,1,1 Imager.Saturation = 1 Imager.Response curve = sRGB Imager.Gamma = 1 For accurate results it is imperative the scene's lighting characteristics are set up properly. That means the power, efficienty, and color temperature settings are correct (physically accurate) on all emmissive objects. Exterior Glazing should be modelled properly with the correct transmission and Set up all Artificial Lights as follows: Use an emmissive Material (Diffuse). For Power to be specified in Lumens; Set Texture Pin to 1/683 or 0.001464129. (1 lumen of light output consumes 1/683 of a watt, or 1 watt produces 683 lumens of light.) Set Power Pin to the Lumen Value of the bulb. Set Temperature Pin to match the bulb output. All materials that let light pass through them and into the space, either from adjacent spaces, or from outside are configured correctly, particularly in terms of transmission and reflection. The sun is configured correctly and the project's proper location in the world is set. The space does not let light leak into it. All exterior walls need to be modelled to enclose the space correctly. Using the application. The application takes the output directly from the current rendertarget. The user can do one or more of three things with the data. Spot Metering. This is simply labeling either the diffuse or luminance image with the luminance values at arbtrary user logations. A mouse drag witll report the value under the cursor and a mouse up will label the point. The labeling of points can be turned on and off. Grid Sampling. This function overlays a sampling grid on the image that reports the Luminance at the grid intersections. The X and Y grid spacing in pixels and the color of the tic and text can be specified under the Settings Tab There is also an option, on by default, to color the grid's Tic Mark and Text, based on the specified theme. Thematic Analysis. This function colors each pixel in the image based on where, in a user specified set of ranges, its value resides. Under the Settings Tab, the Theme Count slider specifies how many color bands there will be in the final result. The Lumen Range determines the variation in lumen values that will be included in a single band. The user can change either one, however they are interconnected, increasing the Lumen Range reduces the Theme Count. The colors to use are also specified here and are the inputs into a gradient used to color all the pixels with a Luminance value from 0 to 45645(max lux per the LBNL Radiance equation). This gradient is visible on the False Color Image tab, if you click on it, it will report the luminance value represented by the underlying color, the ledgend can be placed on the image so you can generate pictures suitable for inclusion in a written report. There is also a histogram on this tab which shows the number of pixels in the image that fall within each band. Reporting Units. The user can set which units the above three function are representing, either Lux or Foot Candels. ]]) ---------------------------------------------------------------------------------------------------------------- -- Global Variables -------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- -- Set initial conditions local PROPS_IMAGE = {} local imgLastRenderResult = nil local imgDIFLastRenderResult = nil local imgLUMLastRenderResult = nil local imgFCLastRenderResult = nil local imgDiffuse = nil local imgLuminance = nil local imgFalseColor = nil --local imgCanvas = nil local tblDefaultGridColor = {} local tblDefaultMarkColor = {} local tblDefaultTextColor = {} local strDIFF_FilePath = nil local strLUMI_FilePath = nil local strFCLR_FilePath = nil -- False Color related variables local tblFalseColors = {} local tblFalseColorPixelCount = {} local numLegendOffsetX = 25 local numLegendOffsetY = 40 -- must be > than bmpLegend.height + 10 so text won't get drawn below bottom edge. local clrLegendStart = {0,0,125,255} local clrLegendMiddle = {155,255,155,255} local clrLegendEnd = {255,255,155,255} local clrHistogramBackground = {72,72,72,255} local numMaxLuminance = 45645 -- For now, needs research, this is the theoretical max based on radiance equation. local numLumensPerTheme = 2283 -- Equivalent of 20 Themes/Contours local numThemeCount = math.ceil((numMaxLuminance/numLumensPerTheme) +.5) -- Sampling Grid related variables local numSamplingGridStepX = 60 local numSamplingGridStepY = 40 local clrSamplingGridTic = {200,200,255,255} local clrSamplingGridText = {200,200,255,255} local blnThemeSamplingGrid = true local numTicSize = 6 local numPointSize = 4 -- Spot Meter related variables local clrSpotMeterTic = {25,25,255,255} local clrSpotMeterText = {25,25,255,255} ---------------------------------------------------------------------------------------------------------------- -- GUI Setup --------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- -- GUI helper labels for padding columns local lblBlank = octane.gui.create { type = octane.gui.componentType.LABEL, text = "", width = 10, } local lblSpace = octane.gui.create { type = octane.gui.componentType.LABEL, text = "", width = 5, height = 2, } -- Create Input Controls local btnFileOpen = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Open...", width = 60, height = 20, x = 0, y = 0, tooltip = "Opens an image file from disk.", } local lblInputPath = octane.gui.create { type = octane.gui.componentType.LABEL, text = " " .. octane.file.getParentDirectory(octane.project.getCurrentProject()), width = 420, tooltip = "Location where processed Images will be saved." } -- Not used - Group Input Controls local grpInput = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 1, cols = 2, --width = bmpDiffuse.width + 30, --height = children = { btnFileOpen, lblInputPath}, border = false, inset = { 5 }, padding = { 5 }, } -- Create Analytic Controls ---------------------------------------------------------------------------------- -- Diffuse Image Controls - tab 1 local lblDiffuseImage = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Original Image:", width = 300, } local lblSpotMeterDiffuse = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spot Meter Value: 0.0", width = 300, x = 12, y = 0, } local btnDrawGridOnDiffuse = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Draw Grid", width = 80, height = 20, x = 0, y = 0, tooltip = "Draws a sampling grid on the Original Diffuse Image and labels it the with Luminance Values retrieved from the image at the specified Grid Tic locations.", } local btnResetDiffuse = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Reset", width = 80, height = 20, x = 0, y = 0, tooltip = "Refreshes the Diffuse Image by recapturing the output from the current Render Target. This will clear any previously drawn Luminance Grid values.", } -- Luminance Image Controls - tab 2 local lblLuminanceImage = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Luminance Image: Greyscale, Response curve is equivalent to Linear/off.", width = 400, x = 0, y = 0, } local lblSpotMeterLuminanceValue = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spot Meter Value: 0.0", width = 300, x = 12, y = 0, } local chkToneMappingLinear = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Linear/off", x = 20, y = 0, width = 70, checked = true, enable = true, tooltip = "Display the Luminance Image where Saturation = 0, Response curve = Linear/off and Gamma = 1, i.e. no tone mapping. The pixel values in the Luminance image correspond to the actual value of the light prior to being converted to lux or foot candles per the LBNL Radiance equation." } local chkToneMappingRGB = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "sRGB", x = 5, y = 0, width = 80, checked = false, enable = true, tooltip = "Display Luminance Image where Saturation = 0, Response curve = sRGB and Gamma = 1, i.e. tone mapped for typical computer monitors as sRGB is a Linear/off image with a gamma of 2.2 applied to it." } local btnResetLuminance = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Reset", width = 80, height = 20, x = 0, y = 0, tooltip = "Processes the image so that each Pixel Value is equal to the Luminance Value at that location on the surface within the scene. This will clear any previously drawn Luminance Grid values.", } local btnDrawGridOnLuminance = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Draw Grid", width = 80, height = 20, x = 0, y = 0, tooltip = "Draws a sampling grid on the Luminance Image and labels it the with Luminance Values retrieved from the image at the specified Grid Tic locations.", } -- False Color Image Controls - tab 3 local lblFalseColorImage = octane.gui.create { type = octane.gui.componentType.LABEL, text = "False Color Image:", width = 300, } local lblLegendMeterFalseColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Legend Value:", width = 300, x = 12, y = 0, } local btnThemeImage = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Theme Image", width = 80, height = 20, x = 0, y = 0, tooltip = "Applies a false color theme to the image based on the underlying luminance values.", } local bmpLegend = octane.gui.create { type = octane.gui.componentType.BITMAP, width = 320, height = 16, x = 90, y = 0, opacity = 1, enable = true, backgroundColour = { 0, 0, 0, 0 }, tooltip = "Click to retrieve the Luminance value represented by that particular color.", mouseCallback = mouseCallback, } local bmpFalseColorHistogram = octane.gui.create { type = octane.gui.componentType.BITMAP, width = bmpLegend.width, height = 100, x = 190, y = 0, opacity = 1, enable = true, backgroundColour = clrHistogramBackground, tooltip = "False Color Histogram.", mouseCallback = mouseCallback, } local bmpLuminanceHistogram = octane.gui.create { type = octane.gui.componentType.BITMAP, width = 320, height = 100, x = 180, y = 0, opacity = 1, enable = true, backgroundColour = { 69,69,69, 255 }, tooltip = "Luminance Histogram.", mouseCallback = mouseCallback, } local btnDrawLegend = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Place Legend", width = 80, height = 20, x = 0, y = 0, tooltip = "Draws a legend on top of the False Color Image. The location can be adjusted on the Settings Tab.", } local btnResetFalseColorImage = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Reset", width = 80, height = 20, x = 0, y = 0, tooltip = "Restores the False Color Image to the original Diffuse Image.", } -- Create Settings Controls - tab 4 local lblLegendLeftOffset = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Left Offset", width = 70, } local nbxLegendLeftOffset = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = true, width = 70, height = 16, x = 0, maxValue = 150, minValue = 0, step = 5, value = numLegendOffsetX, enable = true, tooltip = "Offset from the left edge of the image in pixels.", } -- Group the Legend Left Offset Controls local grpLegendLeftOffset = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = { nbxLegendLeftOffset,lblLegendLeftOffset }, border = false, inset = { 0 }, padding = { 5 }, } local lblLegendBottomOffset= octane.gui.create { type = octane.gui.componentType.LABEL, text = "Bottom Offset", width = 70, } local nbxLegendBottomOffset = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = true, width = 70, height = 16, x = 0, maxValue = 50, minValue = bmpLegend.height + 10, step = 1, value = numLegendOffsetY, enable = true, tooltip = "Offset from the left edge of the image in pixels.", } -- Group the Legend Bottom Offset Controls local grpLegendBottomOffset = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = {nbxLegendBottomOffset,lblLegendBottomOffset }, border = false, inset = { 0 }, padding = { 5 }, } -- Group Legend Offset Controls local grpAllLegendOffsetControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = 100, rows = 1, cols = 2, children = {grpLegendLeftOffset, grpLegendBottomOffset}, border = false, inset = { 0 }, padding = { 5 }, } local lblStartColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Start Color", width = 60, } local clsLegendStartColorPicker = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "START_COLOUR_SWATCH", width = 20, height = 20, colour = clrLegendStart, tooltip = "Click to pick the beginning color.", } -- Group the Start Color Controls local grpStartColor = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = {clsLegendStartColorPicker, lblStartColor }, border = false, inset = { 0 }, padding = { 5 }, } local lblMiddleColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Middle Color", width = 60, } local clsLegendMiddleColorPicker = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "MIDDLE_COLOUR_SWATCH", width = 20, height = 20, colour = clrLegendMiddle, tooltip = "Click to pick the center color.", } -- Group the Middle Color Controls local grpMiddleColor = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = {clsLegendMiddleColorPicker, lblMiddleColor }, border = false, inset = { 0 }, padding = { 5 }, } local lblEndColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "End Color", width = 60, } local clsLegendEndColorPicker = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "END_COLOUR_SWATCH", width = 20, height = 20, colour = clrLegendEnd, tooltip = "Click to pick the last color.", } -- Group the End Color Controls local grpEndColor = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = {clsLegendEndColorPicker, lblEndColor }, border = false, inset = { 0 }, padding = { 5 }, } -- Group Legend Color Controls local grpAllLegendColorControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 2, cols = 3, children = {grpStartColor, grpMiddleColor, grpEndColor, lblSpace, lblSpace, lblSpace}, --children = {lblBlank, lblBlank, lblBlank}, border = false, inset = { 5 }, padding = { 5 }, } local nbxLumensPerTheme = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = true, logarithmic = true, width = 100, height = 16, x = 10, maxValue = 45645, minValue = 500, step = 250, value = numLumensPerTheme, enable = true, tooltip = "Sets the range of Lumen values to include in each color band, e.g. if set to 5000, a band would include all the pixels with values between 4000 to 9000 depending on the initial value.", } local lblLumensPerTheme = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Lumen Range", width = 70, x = 5, } -- Group Lumens Per Theme Controls local grpAllLumensPerThemeControls = octane.gui.create { type = octane.gui.componentType.GROUP, width = 700, rows = 1, cols = 2, children = { nbxLumensPerTheme,lblLumensPerTheme }, border = false, inset = { 0 }, padding = { 0 }, } local nbxThemeCount = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = false, width = 70, height = 16, x = 10, maxValue = 100, minValue = 2, step = 1, value = numMaxLuminance/numLumensPerTheme, enable = true, tooltip = "Sets the number of unique colors or bands, derived from the Legends color gradient used to theme the data.", } local lblThemeCount = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Theme Count", width = 80, x = 5, } -- Group Theme Count Controls local grpAllThemeCountControls = octane.gui.create { type = octane.gui.componentType.GROUP, width = 700, rows = 1, cols = 2, children = { nbxThemeCount,lblThemeCount }, border = false, inset = { 0 }, padding = { 0 }, } -- Group Theme Controls local grpAllThemeControls = octane.gui.create { type = octane.gui.componentType.GROUP, width = 700, rows = 1, cols = 2, children = { grpAllThemeCountControls, grpAllLumensPerThemeControls }, border = false, inset = { 0 }, padding = { 0 }, } -- Group False Color Legend Settings local grpAllFalseColorSettings = octane.gui.create { type = octane.gui.componentType.GROUP, width = 700, x = 0, text = "False Color Legend", rows = 3, cols = 1, children = { grpAllLegendOffsetControls, grpAllLegendColorControls, grpAllThemeControls }, border = true, inset = { 0 }, padding = { 0 }, } -- Sampling Grid Controls local lblGridStepX = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spacing X", width = 70, } local nbxGridStepX = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = true, width = 70, height = 16, x = 0, y = 2, maxValue = 150, minValue = 10, step = 5, value = numSamplingGridStepX, enable = true, tooltip = "Horizontal distance between Grid Tics.", } -- Group GridStepX Controls local grpGridStepXControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = {lblGridStepX, nbxGridStepX}, border = false, inset = { 0 }, padding = { 5 }, } local lblGridStepY = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spacing Y", width = 70, } local nbxGridStepY = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, displaySlider = true, width = 70, height = 16, x = 0, y = 2, maxValue = 150, minValue = 10, step = 5, value = numSamplingGridStepY, enable = true, tooltip = "Vertical distance between Grid Tics.", } -- Group GridStepY Controls local grpGridStepYControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = { lblGridStepY, nbxGridStepY }, border = false, inset = { 0 }, padding = { 5 }, } -- Group Grid Color Controls local clsSampleGridTicColor = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "SAMPLEGRIDTIC_COLOUR_SWATCH", width = 20, height = 20, colour = clrSamplingGridTic, tooltip = "Click to pick the color of the Tic (cross hair) in the Sampling Grid. Legibility is a function of the Thematic Colors defined by the Legend.", } local lblSampleGridTicColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Sampling Grid Tic Color", width = 120, } -- Group Sample Grid Tic Controls local grpSampleGridTicControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = { clsSampleGridTicColor,lblSampleGridTicColor }, border = false, inset = { 0 }, padding = { 5 }, } local clsSampleGridTextColor = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "SAMPLEGRIDTEXT_COLOUR_SWATCH", width = 20, height = 20, colour = clrSamplingGridText, tooltip = "Click to pick the color of the Text in the Sampling Grid. Readability is a function of the Thematic Colors defined by the Legend.", } local lblSampleGridTextColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Sampling Grid Text Color", width = 120, } -- Group Sample Grid Text Controls local grpSampleGridTextControls = octane.gui.create { type = octane.gui.componentType.GROUP, --width = lblHelp:getProperties().width, rows = 1, cols = 2, children = { clsSampleGridTextColor, lblSampleGridTextColor }, border = false, inset = { 0 }, padding = { 5 }, } -- Check box for automatic sample grid text color local chkThemeSamplingGrid = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Theme Text", x = 5, y = 0, width = 120, checked = blnThemeSamplingGrid, enable = true, tooltip = "Themes the Sampling Grid Text based on the Luminance Value at the Grid Tic location it is annotating. i.e. Theme the text based on the False Color Legend." } -- Group Sampling Grid Settings local grpAllSamplingGridSettings = octane.gui.create { type = octane.gui.componentType.GROUP, width = grpAllFalseColorSettings.width, text = "Sampling Grid", rows = 3, cols = 2, children = { grpGridStepXControls,grpGridStepYControls, grpSampleGridTicControls,grpSampleGridTextControls, chkThemeSamplingGrid,lblSpace }, border = true, inset = { 5 }, padding = { 5 }, } -- Group Spot Metering Settings local chkSpotMeterLabelPoints = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Label Points", x = 5, y = 0, width = 120, checked = true, enable = true, tooltip = "Labels the Mouse Up location when using the Spot Metering feature." } local clsSpotMeterTicColor = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "SPOTMETERTIC_COLOUR_SWATCH", width = 20, height = 20, colour = clrSpotMeterTic, tooltip = "Click to pick the color of the Tic in the Spot Meter Tool. Readability is a function of the Thematic Colors defined by the Legend.", } local lblSpotMeterTicColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spot Meter Tic Color", width = 120, } local grpSpotMeterTicSettings = octane.gui.create { type = octane.gui.componentType.GROUP, width = grpAllFalseColorSettings.width, rows = 1, cols = 2, children = { clsSpotMeterTicColor,lblSpotMeterTicColor}, border = false, inset = { 0 }, padding = { 5 }, } -- Spot Meter Settings local clsSpotMeterTextColor = octane.gui.create { type = octane.gui.componentType.COLOUR_SWATCH, name = "SPOTMETERTEXT_COLOUR_SWATCH", width = 20, height = 20, colour = clrSpotMeterText, tooltip = "Click to pick the color of the Text in the Spot Meter Tool. Readability is a function of the Thematic Colors defined by the Legend.", } local lblSpotMeterTextColor = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Spot Meter Text Color", width = 120, } local grpSpotMeterTextSettings = octane.gui.create { type = octane.gui.componentType.GROUP, width = grpAllFalseColorSettings.width, rows = 1, cols = 2, children = { clsSpotMeterTextColor,lblSpotMeterTextColor}, border = false, inset = { 0 }, padding = { 5 }, } local grpAllSpotMeterSettings = octane.gui.create { type = octane.gui.componentType.GROUP, width = grpAllFalseColorSettings.width, text = "Spot Meter", rows = 2, cols = 2, children = { chkSpotMeterLabelPoints, lblSpace, grpSpotMeterTicSettings, grpSpotMeterTextSettings }, border = true, inset = { 5 }, padding = { 5 }, } -- Units of Measure local chkIlluminanceUnitLux = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Lux (lumens/sqm)", x = 5, y = 0, width = 160, checked = false, enable = true, tooltip = "Sets the units to be reported in lux." } local chkIlluminanceUnitFootCandle = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Foot Candles (lumens/sqft)", x = 5, y = 0, width = 160, checked = true, enable = true, tooltip = "Sets the units to be reported in foot candals." } -- Group Units of Measure local grpAllUnitsOfMeasure = octane.gui.create { type = octane.gui.componentType.GROUP, width = grpAllFalseColorSettings.width, text = "Reporting Units", rows = 1, cols = 2, children = { chkIlluminanceUnitLux , chkIlluminanceUnitFootCandle }, border = true, inset = { 0 }, padding = { 5 }, } -- Group Analytic Controls local grpAnalyticsTab1a = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 1, cols = 2, --width = 380, --height = children = { btnDrawGridOnDiffuse, btnResetDiffuse }, border = false, inset = { 5 }, padding = { 5 }, } -- Diffuse Controls local grpAnalyticsTab1 = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 4, cols = 1, --width = 380, --height = children = { lblDiffuseImage, lblSpace, lblSpotMeterDiffuse, grpAnalyticsTab1a }, border = false, inset = { 5 }, padding = { 5 }, } -- Luminance Controls local grpAnalyticsTab2a = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 1, cols = 4, --width = 380, --height = children = { btnDrawGridOnLuminance, btnResetLuminance, chkToneMappingLinear, chkToneMappingRGB}, border = false, inset = { 5 }, padding = { 5 }, } local grpAnalyticsTab2 = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 4, cols = 1, --width = 380, --height = children = { lblSpace, lblSpace, lblSpotMeterLuminanceValue, grpAnalyticsTab2a }, border = false, inset = { 5 }, padding = { 5 }, } -- False Color Controls local grpAnalyticsTab3a = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 2, cols = 3, --width = 380, --height = children = { btnThemeImage,btnResetFalseColorImage,lblSpace,btnDrawLegend,bmpLegend,lblSpace }, border = false, inset = { 5 }, padding = { 5 }, } local grpAnalyticsTab3 = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 5, cols = 1, --width = 380, --height = children = { lblFalseColorImage, lblSpace, lblLegendMeterFalseColor, grpAnalyticsTab3a, lblSpace}, border = false, inset = { 5 }, padding = { 5 }, } -- Settings Controls local grpAnalyticsTab4 = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 4, cols = 1, --width = 380, --height = children = { grpAllUnitsOfMeasure, grpAllFalseColorSettings, grpAllSamplingGridSettings, grpAllSpotMeterSettings, }, --},, -- }, border = false, inset = { 5 }, padding = { 5 }, } -- Tab to hold Analytic & Settings Controls local tabImages = octane.gui.create { type = octane.gui.componentType.TABS, children = { grpAnalyticsTab1, grpAnalyticsTab2, grpAnalyticsTab3, grpAnalyticsTab4 }, -- components, 1 for each tab header = { "Original Image", "Luminance Image", "False Color Image", "Settings"}, -- text to appear on each tab currentTab = 2, -- initially selected tab callback = function(component, event) print(" ") print("Current tab: ", component:getProperties().currentTab) end -- prints selected tab } -- Progress bar local pbrProcessImage = octane.gui.create { type = octane.gui.componentType.PROGRESS_BAR, progress = 50, width = 495, height = 8, x = 0, y = -5, } -- Group Output Controls local grpProgressBarControls = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 1, cols = 1, children = { pbrProcessImage }, border = false, inset = { 5 }, padding = { 5 }, } -- Create Output Controls local btnFileSave = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Save", width = 60, height = 20, enable = false, x = 0, y = 0, tooltip = "Saves the current Analysis Image to disk.", } -- Controls for Help form ------------------------------------------------------------- local btnHelpOpen = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Help...", x = 290, y = 0, width = 60, height = 20, tooltip = "Loads a Form with information about the script.", } local lblHelp = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Help Text", --x = 10, --y = 10, width = 650, height = 12, } local txtHelp = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, multiline = true, text = "", width = lblHelp:getProperties().width, --x = 10, height = 355, tooltip = "" } local btnHelpClose = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Close", width = 60, height = 20, x = lblHelp:getProperties().width - 60, y = 10, tooltip = "Closes the Help window.", } -- Group the help controls local grpHelp = octane.gui.create { type = octane.gui.componentType.GROUP, --width = grpInputFileContent:getProperties().width, rows = 2, cols = 1, children = {txtHelp, btnHelpClose}, border = false, inset = { 5 }, padding = { 5 }, } -- Window that holds the Help controls local wndHelp = octane.gui.create { type = octane.gui.componentType.WINDOW, text = "Luminance Analysis Help", children = {grpHelp}, width = lblHelp:getProperties().width + 60, height = txtHelp:getProperties().height + 40, padding = { 12 }, inset = { 12 }, } local btnMainClose = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Close", width = 60, height = 20, x = 0, y = 0, tooltip = "Closes the Application.", } local grpSaveClose = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 1, cols = 3, children = { btnFileSave, btnHelpOpen, btnMainClose }, border = false, inset = { 5 }, padding = { 5 }, } -- Group Output Controls local grpOutput = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 2, cols = 1, children = { grpProgressBarControls,grpSaveClose}, border = false, inset = { 5 }, padding = { 5 }, } ---------------------------------------------------------------------------------------------------------------- -- Functions --------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- function showWarning(text,msg) local ret = octane.gui.showDialog { type = octane.gui.dialogType.BUTTON_DIALOG, buttons = 2, buttonTexts = {"Ok"}, icon = octane.gui.dialogIcon.WARNING, title = "WARNING:\n"..tostring(text), text = tostring(msg), } end -- Helper Function to analyse steps in the calculation of lumens function calcLumens() print(" ") print("@calcLumens") print(" ") -- Values from image local R = 1 local G = 1 local B = 1 for a = 1, 11, 1 do -- Normalized values from image local nR = R/255 local nG = G/255 local nB = B/255 local lR = ((R/255) <= 0.04045 and (R/255)/12.92 or math.pow(((R/255)+0.055)/1.055, 2.4)) local lG = ((G/255) <= 0.04045 and (G/255)/12.92 or math.pow(((G/255)+0.055)/1.055, 2.4)) local lB = ((B/255) <= 0.04045 and (B/255)/12.92 or math.pow(((B/255)+0.055)/1.055, 2.4)) print("Using Gamma free Linear values") print(" Image's RGB ", R,G,B) print(" normalized RGB ", nR, nG, nB) print(" remove Gamma ", lR, lG, lB) print(" reapply Gamma ", gam_sRGB(lR), gam_sRGB(lG), gam_sRGB(lB)) print(" LBNL grey ", (0.265 * lR + 0.670 * lG + 0.065 * lB)) -- max possible Value = 1 print(" LBNL luminance ", 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB)) -- max possible Value = 179 print(" LBNL grey * 255 ", (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255) -- What is stored in the luminance image print(" LBNL luminance * 255 ", 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255) -- What is reported in measurements print(" ") print("Using Normalized values") print(" Image's RGB ", R,G,B) print(" normalized RGB ", nR, nG, nB) print(" LBNL grey ", (0.265 * nR + 0.670 * nG + 0.065 * nB)) print(" LBNL luminance ", 179 * (0.265 * nR + 0.670 * nG + 0.065 * nB)) print(" LBNL grey * 255 ", (0.265 * nR + 0.670 * nG + 0.065 * nB) * 255) print(" LBNL luminance * 255 ", 179 * (0.265 * nR + 0.670 * nG + 0.065 * nB) * 255) print(" ") print(" ") print(" ") R = R + 24 G = G + 24 B = B + 24 end print("Exiting calcLumens") end -- Helper Function to print a table -- Print the entries in a table identified by their key function tprint (tblInput, strRecordDescription) -- s is a programer's note that can be used to -- describe each entry in the table if strRecordDescription == nil then strRecordDescription = "Table entry" end print(" ") print("@tprint") print(" Inputs: ", tblInput, strRecordDescription) print(" ") -- key/value pairs for k, v in pairs(tblInput) do -- Put in quotes local kformat = '["' .. tostring(k) ..'"]' -- Remove quotes from non string types if type(k) ~= 'string' then kformat = '[' .. k .. ']' end local vformat = '"'.. tostring(v) ..'"' if type(v) == 'table' then tprint(v, (strRecordDescription or '')..kformat) else if type(v) ~= 'string' then vformat = tostring(v) end -- print(type(tblInput)..(strRecordDescription or '') ..kformat..' = '..vformat) -- print(" Table entry "..(strRecordDescription or '') ..kformat..' = '..vformat) print(" " ..(strRecordDescription or '') .." ".. kformat..' = '..vformat) end end print(" ") end -- Not Used function getImageInfo() local tblImageInfo = {} -- NT_TEX_RGB == node type 33 -- NT_TEX_IMAGE == node type 34 (seems this is the RGB image) -- NT_TEX_ALPHAIMAGE = node type 35 -- NT_TEX_FLOATIMAGE == node type 36 (seems this is a greyscale image) -- Get user selected image texture node and add it to the table --tblImageInfo.node = octane.project.getSelection()[1] if tblImageInfo.node == nil then print("Node Type selected by the user = nil", tblImageInfo.node:getProperties().type) showWarning("No node is selected.", "Please select an image texture node.") return end if tblImageInfo.node:getProperties().type == 34 -- rgba or tblImageInfo.node:getProperties().type == 35 -- alpha or tblImageInfo.node:getProperties().type == 36 then -- greyscale print("Node Type selected by the user =", tblImageInfo.node:getProperties().type) else print("Node Type selected by the user =", tblImageInfo.node:getProperties().type) showWarning("Selected node is of incorrect type.", "Please select an image texture node.") return end assert(tblImageInfo.node and tblImageInfo.node:getProperties().type == 34 -- rgba or tblImageInfo.node:getProperties().type == 35 -- alpha or tblImageInfo.node:getProperties().type == 36, -- greyscale "Select an RGB image texture node.") -- Get other useful info about the image texture tblImageInfo.size = tblImageInfo.node:getAttribute(octane.A_SIZE) tblImageInfo.texels = tblImageInfo.node:getAttribute(octane.A_BUFFER) tblImageInfo.filepath = tblImageInfo.node:getAttribute(octane.A_FILENAME) print(string.format("texture %d x %d, buffer len %d", tblImageInfo.size[1], tblImageInfo.size[2], #tblImageInfo.texels)) return tblImageInfo end -- Creates an RGBA texture node with the image of the -- last rendertargets output within it. function getLastRenderResult() print(" ") print("@getLastRenderResult") local tblRenderResults = octane.render.grabRenderResult() imgLastRenderResult = tblRenderResults[1].image local tblImageProperties = imgLastRenderResult:getProperties() local pixels = {} print(" Content of tblRenderResults:") tprint(tblRenderResults) local nodeImageTexture = octane.node.create { type = octane.NT_TEX_IMAGE, name = "Last Luminance Analysis Render Result"} --, position = { 600, 650} } if not imgLastRenderResult:fillImageNode(nodeImageTexture) then error("Could not fill the source image node with the image.") end print("Exiting LastRenderResult") print(" ") print(" ") end local function getMONOPixel(imgTexture, x, y) local ix = 1 * ((x-1) + imgTexture.size[1] * (y-1)) + 1 return { imgTexture.texels[ix], imgTexture.texels[ix+1], imgTexture.texels[ix+2], imgTexture.texels[ix+3] } end -- Gets a pixel from an image buffer, -- not to be confused with api call octane.image.getPixel(image, x, y) local function getRGBAPixel(tblImageInfo, x, y) local ix = 4 * ((x-1) + tblImageInfo.size[1] * (y-1)) + 1 return { tblImageInfo.buffer[ix], tblImageInfo.buffer[ix+1], tblImageInfo.buffer[ix+2], tblImageInfo.buffer[ix+3] } end -- Not used local function setAPixel(imgTexture, x, y, cl) -- Requires an image to be set up per top of grawGradient function -- Bounds check if x > imgTexture.size[1] or x < 1 then return end if y > imgTexture.size[2] or y < 1 then return end local ix = 4 * ((x-1) + imgTexture.size[1] * (y-1)) + 1 -- Bounds check on the index if (ix > #imgTexture.texels) then return end imgTexture.buffer[ix] = cl[1] imgTexture.buffer[ix+1] = cl[2] imgTexture.buffer[ix+2] = cl[3] imgTexture.buffer[ix+3] = cl[4] end -- Apply "Gamma" to Linear (approx 2.2) -- takes a single channel value (0-1) of a Linear color -- and returns the corresponding single chanel sRGB color value(0-255) local function gam_sRGB(v) -- Takes a single linear color channel value as input if v <= 0.0031308 then v = v * 12.92 else v = 1.055 * v^(1.0/2.4) - 0.055 end return math.floor(v * 255 + 0.5) -- This is correct in Lua end -- Remove "Gamma" from sRGB (approx 2.2) -- takes a single channel value (0-255) of an sRGB color -- normalizes it, and returns the corresponding single -- channel Linear color value(0-1) local function inv_gam_sRGB(ic) -- Takes a single sRGB channel value as input local c = ic/255.0 if c <= 0.04045 then return c/12.92 else return ((c + 0.055)/(1.055))^2.4 end end -- Greyscale("luminance") function -- takes 3 standard color values (0-255), removes the Gamma -- (approx 2.2), applies the appropriate sRGB luminance(Y) value -- to each channel and returns the single a Linear greyscale value -- This value needs to be multiplied by 255 to use with Octanes LDR image types local function getRGB_Coefficient(sR, sG, sB) -- Takes the three sRGB channels as input -- sRGB luminance(Y) values local rY = 0.212655 local gY = 0.715158 local bY = 0.072187 return gam_sRGB( inv_gam_sRGB(sR) * rY + inv_gam_sRGB(sG) * gY + inv_gam_sRGB(sB) * bY ) end -- Greyscale("luminance") function -- takes the 3 spectral color values (0-255) of an sRGB color -- and returns the corresponding Linear greyscale value(0-1) -- This value can be used directly with Octanes LDR image types function getRGB_Luminance(sR, sG, sB) local v = 0 v = v + 0.212655 * ((sR/255) <= 0.04045 and (sR/255)/12.92 or math.pow(((sR/255)+0.055)/1.055, 2.4)) v = v + 0.715158 * ((sG/255) <= 0.04045 and (sG/255)/12.92 or math.pow(((sG/255)+0.055)/1.055, 2.4)) v = v + 0.072187 * ((sB/255) <= 0.04045 and (sB/255)/12.92 or math.pow(((sB/255)+0.055)/1.055, 2.4)) return 255*v <= 0.0031308 and v*12.92 or 1.055 * math.pow(v,1.0/2.4) - 0.055 end -- Helper Function to analyse steps in the calculation of lumens function calcLumens() print(" ") print("@calcLumens") print(" ") -- Values from image local R = 1 local G = 1 local B = 1 for a = 1, 11, 1 do -- Normalized values from image local nR = R/255 local nG = G/255 local nB = B/255 local lR = ((R/255) <= 0.04045 and (R/255)/12.92 or math.pow(((R/255)+0.055)/1.055, 2.4)) local lG = ((G/255) <= 0.04045 and (G/255)/12.92 or math.pow(((G/255)+0.055)/1.055, 2.4)) local lB = ((B/255) <= 0.04045 and (B/255)/12.92 or math.pow(((B/255)+0.055)/1.055, 2.4)) print("Using Gamma free Linear values") print(" Image's RGB ", R,G,B) print(" normalized RGB ", nR, nG, nB) print(" remove Gamma ", lR, lG, lB) print(" reapply Gamma ", gam_sRGB(lR), gam_sRGB(lG), gam_sRGB(lB)) print(" LBNL grey ", (0.265 * lR + 0.670 * lG + 0.065 * lB)) -- max possible Value = 1 print(" LBNL luminance ", 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB)) -- max possible Value = 179 print(" LBNL grey * 255 ", (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255) -- What is stored in the luminance image print(" LBNL luminance * 255 ", 179 * (0.265 * lR + 0.670 * lG + 0.065 * lB) * 255) -- What is reported in measurements print(" ") print("Using Normalized values") print(" Image's RGB ", R,G,B) print(" normalized RGB ", nR, nG, nB) print(" LBNL grey ", (0.265 * nR + 0.670 * nG + 0.065 * nB)) print(" LBNL luminance ", 179 * (0.265 * nR + 0.670 * nG + 0.065 * nB)) print(" LBNL grey * 255 ", (0.265 * nR + 0.670 * nG + 0.065 * nB) * 255) print(" LBNL luminance * 255 ", 179 * (0.265 * nR + 0.670 * nG + 0.065 * nB) * 255) print(" ") print(" ") print(" ") R = R + 24 G = G + 24 B = B + 24 end print("Exiting calcLumens") end function drawGradient(bmpComponent, clrStart, clrMiddle, clrEnd) -- Add gradient 'Type' ( 3 color, 4 color, argument at some point print(" ") print("@drawGradient") -- Handle missing inputs, should never happen since they are declared as global if clrStart == nil then clrStart = { 0 ,255,255 } end -- cyan if clrMiddle == nil then clrMiddle = { 255,0 ,255 } end -- magenta if clrEnd == nil then clrEnd = { 255,255,125 } end -- yellow -- Strip 4-component vector of it's alpha channel as lerp only wants 3 if #clrStart > 3 then clrStart = { clrStart[1], clrStart[2], clrStart[3] } end if #clrMiddle > 3 then clrMiddle = { clrMiddle[1], clrMiddle[2], clrMiddle[3] } end if #clrEnd > 3 then clrEnd = { clrEnd[1], clrEnd[2], clrEnd[3] } end tprint(clrStart) tprint(clrMiddle) tprint(clrEnd) -- Create a table of image info local tblImageInfo = {} tblImageInfo.size = { bmpComponent.width, bmpComponent.height } tblImageInfo.type = 0 -- octane.image.type.LDR_RGBA tblImageInfo.buffer = {} -- to store the pixels clr1 = clrStart clr2 = clrMiddle -- 3 Band Vertical Gradient -- Fill the buffer with a gradient, logically we divided the legends width into an even -- number of bands and every even numbered band is a mirror of every odd numbered band local numBandCount = 1 -- Band Count local numBands = 2 -- i.e. half the control width for a middle color/ must be an even # local numWidthOfBand = bmpComponent.width/numBands -- proportion of control to paint local t = 0 local numBufferLength = (tblImageInfo.size[1] * tblImageInfo.size[2]) for i = 1, numBufferLength do -- every pixel -- Interpolate between start & end color, needs 3-component vector local clrNew = octane.vec.lerp(clr1, clr2, t) -- Write 4 values into the buffer representing the color table.insert(tblImageInfo.buffer, clrNew[1]) table.insert(tblImageInfo.buffer, clrNew[2]) table.insert(tblImageInfo.buffer, clrNew[3]) table.insert(tblImageInfo.buffer, 255) -- alpha -- If remainder not 0, we are not at the end of a row, interpolate the color -- length of a 'row' is determined by the number of bands, more bands = shorter 'row' -- If t = 0 we start the color over, but we are at the second half of the control -- so we need to switch colors if (i % numWidthOfBand ~= 0) then -- @ an even numbered band t = t + 1/numWidthOfBand else -- Resets the color to the start color t = 0 numBandCount = numBandCount + 1 end if (numBandCount % 2 ~= 0) then -- @ an odd numbered band clr1 = clrStart clr2 = clrMiddle else clr1 = clrMiddle clr2 = clrEnd end -- Two color Horizontal Gradient -- See if we have written out enough for a whole row, if so interpolate a new color -- If the remainder is 0 then new color, on a new horizontal line --if (i % bmpComponent.width == 0) then t = t + 1/bmpComponent.width end -- Two color Vertical Gradient -- See if we have written out enough for a whole row, if not interpolate a new color -- If the remainder is 0 then new row, set interpolator back to the beginning i.e 0 --if (i % bmpComponent.width ~= 0) then t = t + 1/bmpComponent.width else t = 0 end end -- Fill the bitmap with the buffer's contents -- First setup an empty image and assign it to the bitmap component PROPS_IMAGE = {type = 0, size = {tblImageInfo.size[1], tblImageInfo.size[2]}} tprint(PROPS_IMAGE) -- Create the new Image local imgCanvas = octane.image.create(PROPS_IMAGE) print(" imgCanvas dimensions = ", imgCanvas.size[1] .." x ".. imgCanvas.size[2]) -- looks better starting at x = 2 for some reason for x = 2, tblImageInfo.size[1] ,1 do -- Looping needs to be reduced by 1 for some unknown reason for y = 1, tblImageInfo.size[2]-1 ,1 do -- Need to flip the texture to display it properly, why? imgCanvas:setPixel(x, y, getRGBAPixel(tblImageInfo, x, y)) end end bmpComponent:updateProperties{image = imgCanvas} -- Calculate a table of colors, store them in global table tblFalseColors -- First, clear false color table of any previous values local clrTheme = {} tblFalseColors = {} -- Initial value of numThemeCount (global variable) is set at beginning of script print(" Value of numThemeCount @drawGradient =", numThemeCount ) print(" Calculated value of Color Step =", math.floor(bmpComponent.width/numThemeCount) ) -- Don't sample the first or last pixel in the Legend for i = 2, bmpComponent.width, math.floor(bmpComponent.width/numThemeCount)-1 do -- Get color from row 7 in the bmpComponent, i.e. the legend clrTheme = octane.image.getPixel(imgCanvas, i, 7) table.insert(tblFalseColors, clrTheme) end for i = bmpComponent.width/(numThemeCount), bmpComponent.width-1, bmpComponent.width/(numThemeCount) do -- Draw Graticule octane.image.setPixel(bmpComponent.image, i, 10, { 125,125,125,255 }) octane.image.setPixel(bmpComponent.image, i, 11, { 125,125,125,255 }) octane.image.setPixel(bmpComponent.image, i, 12, { 125,125,125,255 }) octane.image.setPixel(bmpComponent.image, i, 13, { 125,125,125,255 }) end print(" Length Table of False Colors", #tblFalseColors) print(" Table of False Colors @drawGradient:") tprint(tblFalseColors) -- Stop the jumping around of a bitmap when it is loaded bmpComponent:updateProperties{y = 8} end function drawHistogram(bmpComponent, numThemeCount) print(" ") print("@drawHistogram") --octane.image.fill(bmpComponent.image, nil, clrHistogramBackground, false) --bmpComponent:updateProperties{} --local imgFCHCanvas = octane.image.create({type = 0, size = {bmpFalseColorHistogram.width, bmpFalseColorHistogram.height}}) --bmpFalseColorHistogram:updateProperties{image = imgFCHCanvas} print(" numThemeCount =", numThemeCount) print(" Number of bands to process =", numThemeCount) print(" Histogram bitmap dimensions: ", bmpComponent.width, bmpComponent.height, bmpComponent.width * bmpComponent.height) local numWidthOfBand = (math.ceil((bmpComponent.width / numThemeCount))) - 1 print(" numWidthOfBand =", numWidthOfBand ) print(" Legend graphic width =", numWidthOfBand * numThemeCount ..",", "must not exceed", bmpComponent.width) -- Due to rounding, the pixel width of the histogram might be smaller than the width of the control -- This is used to center it the graphic in the control local numGraphicOffset = math.floor((bmpComponent.width - (numWidthOfBand * numThemeCount))/2) print(" Value of numGraphicOffset =", numGraphicOffset) -- First setup an empty image and assign it to the bitmap component PROPS_IMAGE = {type = 0, size = {bmpComponent.width, bmpComponent.height}} --tprint(PROPS_IMAGE) -- Create the new Image to paint on local imgCanvas = octane.image.create(PROPS_IMAGE) print(" imgCanvas dimensions = ", imgCanvas.size[1] .." x ".. imgCanvas.size[2]) local x = 0 -- Calculate theme colors for histogram -- interate through each band for i = 1, numThemeCount, 1 do -- Get the color of the band - stored by the drawGradient function, should align with the pixel count bands local clrBand = tblFalseColors[i] --print(" Contents of tblFalseColors entry: " ..i ) --tprint(clrBand) print(" ") print(" Processing band number " ..i) print(" Legend graphic width =", numWidthOfBand * numThemeCount .." pixels") -- Must not exceed bmpLegend width print(" Value of numGraphicOffset =", numGraphicOffset) -- Get the number of pixels in the band - stored by the themeImage function, should align with false color bands local numPixelsInBand = tblFalseColorPixelCount[i] print(" Value of numPixelsInBand in band", i .." = ".. numPixelsInBand ) -- Normalize the pixel count and multiply it by the height of the bitmap control print(" Normalized Height of band =", (numPixelsInBand / (bmpFalseColor.width * bmpFalseColor.height))) local numHeightOfBand = math.ceil(bmpComponent.height * (numPixelsInBand / (bmpFalseColor.width * bmpFalseColor.height))) print(" Calculated Height of band =", numHeightOfBand) -- Draw the band -- For each column in the band for j = numGraphicOffset+1, numWidthOfBand+numGraphicOffset, 1 do for k = 1, numHeightOfBand, 1 do -- Paint it from bottom up i.e. (bmpComponent.height - k) -- print(" ", j + x, bmpComponent.height - k) octane.image.setPixel(imgCanvas, j + x, bmpComponent.height - k, clrBand) end end -- Move over 1 more band, allow for 1 pixel gap x = x + numWidthOfBand --+ 1 --print(" ",bmpComponent.image) -- Stop the jumping around of a bitmap when it is loaded --octane.image.copyRegion(bmpComponent.image, imgCanvas, nil, {0,0}, true) bmpComponent:updateProperties{image = imgCanvas} bmpComponent:updateProperties{x = 185, y = 0} end bmpComponent:updateProperties{} print(" ") print("Exiting drawHistogram") end function themeImage(bmpComponent, numInitialValue, numLumensPerTheme, numThemeCount) print(" ") print("@themeImage") print(" numThemeCount =", numThemeCount) print(" Number of bands to process =", numThemeCount) print(" Contour interval, lumens per band =", numLumensPerTheme) print(" Total number of pixels in image = ", bmpComponent.width * bmpComponent.height) print(" Output Image Type = ", bmpComponent.image.type) -- Clear previous histogram --octane.image.fill(bmpFalseColorHistogram.image, nil, clrHistogramBackground, false) if numInitialValue == nil then numInitialValue = 0 end if numLumensPerTheme == nil then numLumensPerTheme = math.ceil(numMaxLuminance/numThemeCount) end if numThemeCount == nil then numThemeCount = math.ceil((numMaxLuminance/numLumensPerTheme)+.5) end print(" Revised value of inputs ", bmpComponent, numInitialValue, numLumensPerTheme, numThemeCount) --print(" False Colors @themeImage:") -- tblFalseColors gets set in drawGradient Function --tprint(tblFalseColors) tblFalseColorPixelCount = {} numPixelsChanged = 0 for c = 1, numThemeCount , 1 do print(" ") print(" Processing contour number", c) print(" Initial Value for contour " ..c.. " =", numInitialValue) print(" Color =", tblFalseColors[c][1], tblFalseColors[c][2], tblFalseColors[c][3], tblFalseColors[c][4]) print(" Image to sample:", bmpDiffuse.image) print(" Image to paint:", bmpComponent.image) pbrProcessImage:updateProperties{progress = 0, text = ""} local numProgress = (c/numThemeCount) pbrProcessImage:updateProperties{progress = numProgress, text = ""} octane.gui.updateStatus("Themeing image...", numProgress) octane.gui.dispatchGuiEvents(5) for y = 1, bmpComponent.height, 1 do for x = 1, bmpComponent.width, 1 do -- Problem sampling luminance image if it has a sampling grid already local pixOriginalValue = octane.image.getPixel(imgLuminance, x, y) local numLuminance = 179 * pixOriginalValue[1] -- If the Luminance value is within range, paint the pixel if numLuminance > numInitialValue and numLuminance < (numInitialValue + numLumensPerTheme) then octane.image.setPixel(bmpComponent.image, x, y, tblFalseColors[c]) -- Increment the pixel count numPixelsChanged = numPixelsChanged + 1 end end -- x end -- y bmpComponent:updateProperties{} -- Make sure there is at least 1 pixel changed per band table.insert(tblFalseColorPixelCount, numPixelsChanged + 1) numInitialValue = numInitialValue + numLumensPerTheme print(" Number of pixels changed in contour " ..c.. " =", numPixelsChanged) numPixelsChanged = 0 octane.gui.dispatchGuiEvents(5) end -- c -- Update the image bmpComponent:updateProperties{} drawHistogram(bmpFalseColorHistogram, numThemeCount) pbrProcessImage:updateProperties{progress = 100, text = ""} octane.gui.updateStatus("Themeing Complete", 100) octane.gui.dispatchGuiEvents(5) print(" Total number of pixels = ", bmpComponent.width * bmpComponent.height) end function drawGrid(bmpComponent, numStepX, numStepY, clrTic, clrText, numSize ) print(" ") print("@drawGrid") -- Set Crosshair properties if numSize == nil then numSize = numTicSize end if clrTic == nil then clrTic = clrSamplingGridTic end if clrText == nil then clrText = clrSamplingGridText end -- Draw the Grid for i = numStepX/2, bmpComponent.width - numStepX/2, numStepX do pbrProcessImage:updateProperties{progress = 0, text = ""} octane.gui.dispatchGuiEvents(5) local numProgress = (i/(bmpComponent.width - numStepX/2)) pbrProcessImage:updateProperties{progress = numProgress, text = ""} for j = numStepY/2, bmpComponent.height - numStepY/2, numStepY do -- Draw Crosshair drawGridTic(bmpComponent, i, j, clrTic, clrText, numSize) end bmpComponent:updateProperties{} end pbrProcessImage:updateProperties{progress = 100, text = ""} octane.gui.dispatchGuiEvents(5) end function drawGridTic(bmpComponent, pX, pY, clrTic, clrText, numSize) print(" ") print("@drawGridTic") print(" Input location x,y =", pX,pY) local tblPixelValue = {} -- Set Crosshair properties if numSize == nil then numSize = numTicSize end if clrTic == nil then clrTic = clrSamplingGridTic end if clrText == nil then clrText = clrSamplingGridText end -- Bounds Check if chkIlluminanceUnitLux.checked == true then -- For lux digits, max width of a string in pixels = 30, max height = 7 if pX > bmpComponent.width - 31 or pX < 1 then return end if pY > bmpComponent.height - 8 or pY < 1 then return end else -- For fc digits, max width of a string in pixels = 30, max height = 7 if pX > bmpComponent.width - 25 or pX < 1 then return end if pY > bmpComponent.height - 8 or pY < 1 then return end end -- Bounds check for Tics, incase user sets Tics larger than digit string if pX > bmpComponent.width - numSize or pX < 1 + numSize then return end if pY > bmpComponent.height - numSize or pY < 1 + numSize then return end -- Calculate theme colors for tic and text if chkThemeSamplingGrid.checked == true then print(" @Calculating Theme Colors") tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLuminance = 179 * tblPixelValue[1] for i = 1, #tblFalseColors, 1 do if numLuminance > (i-1) * numLumensPerTheme and numLuminance < i * numLumensPerTheme then clrTic = tblFalseColors[i] clrText = tblFalseColors[i] end end end if chkThemeSamplingGrid.checked == false then print(" @Drawing Cross Hairs") tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLuminance = 179 * tblPixelValue[1] print(" Value of numLuminance =", numLuminance) drawDigit(numLuminance, bmpComponent, pX, pY, clrText ) -- Draw crosshair for i = pX - numSize, pX + numSize do for j = pY - numSize, pY + numSize do octane.image.setPixel(bmpComponent.image, i, pY, clrTic) octane.image.setPixel(bmpComponent.image, pX, j, clrTic) end end else print(" @Drawing Thematic Points") tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLuminance = 179 * tblPixelValue[1] print(" Value of numLuminance =", numLuminance) drawDigit(numLuminance, bmpComponent, pX, pY, clrText ) for i = pX - numPointSize/2, pX + numPointSize/2 do for j = pY - numPointSize/2, pY + numPointSize/2 do octane.image.setPixel(bmpComponent.image, i, j, clrTic) end end end -- Set dot in the middle of points or cross hairs back to original value -- This enables diffuse image to be sampled after luminance image since -- pixels at sample location are unchanged. octane.image.setPixel(bmpComponent.image, pX, pY, tblPixelValue) -- Update the image bmpComponent:updateProperties{} end function drawDigit(strDigits, bmpComponent, pX, pY, tblTextColor, oX, oY) print(" ") print("@drawDigit") print(" Values of Inputs: strDigits =", strDigits ) if chkIlluminanceUnitFootCandle.checked == true then print(" Converting Lumens to foot Candles") strDigits = strDigits/10.752 end strDigits = math.ceil(strDigits) strDigits = tostring(strDigits) print(" Values to draw: strDigits =", strDigits ) if tblTextColor == nil then tblTextColor = { 255, 255, 0, 255 } end -- Yellow -- Setup Offsets if oX == nil then xOffset = 6 else xOffset = oX end -- Good values for labelling grid points relative to a tic mark. if oY == nil then yOffset = 3 else yOffset = oY end -- Good values for labelling grid points relative to a tic mark. local dOffset = 0 -- delta for placement of each subsequent digit from original pX & pY, increments by iOffset in loop local iOffset = 6 -- determines spacing between digits, digits are 5 pixels wide, value of 6 is tightest spacing local x = pX + xOffset local y = pY + yOffset print(" x = ", x) print(" y = ", y) imgCanvas = bmpComponent.image -- Loop thru each digit in input string for strDigit in strDigits:gmatch"." do if strDigit == "0" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "1" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "2" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "3" then -- 1st - Top Line of Digit octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "4" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "5" then -- 1st - Top Line of Digit octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "6" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "7" then -- 1st - Top Line of Digit octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "8" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "9" then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 elseif strDigit == "." then -- 1st - Top Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+1, tblTextColor) -- dOffset + x+1,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+1, tblTextColor) -- dOffset + x+2,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+1, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+1, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+1, tblTextColor) -- dOffset + x+5,y+1 -- 2nd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+2, tblTextColor) -- dOffset + x+0,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+2, tblTextColor) -- dOffset + x+3,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+2, tblTextColor) -- dOffset + x+4,y+2 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+2, tblTextColor) -- dOffset + x+5,y+2 -- 3rd --octane.image.setPixel(imgCanvas, dOffset + x+1, y+3, tblTextColor) -- dOffset + x+0,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+3, tblTextColor) -- dOffset + x+4,y+3 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+3, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+3, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+3, tblTextColor) -- dOffset + x+5,y+1 -- 4th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+4, tblTextColor) -- dOffset + x+0,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+4, tblTextColor) -- dOffset + x+4,y+4 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+4, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+4, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+4, tblTextColor) -- dOffset + x+5,y+1 -- 5th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+5, tblTextColor) -- dOffset + x+0,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+2, y+5, tblTextColor) -- dOffset + x+4,y+5 --octane.image.setPixel(imgCanvas, dOffset + x+3, y+5, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+5, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+5, tblTextColor) -- dOffset + x+5,y+1 -- 6th --octane.image.setPixel(imgCanvas, dOffset + x+1, y+6, tblTextColor) -- dOffset + x+0,y+6 octane.image.setPixel(imgCanvas, dOffset + x+2, y+6, tblTextColor) -- dOffset + x+4,y+6 octane.image.setPixel(imgCanvas, dOffset + x+3, y+6, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+6, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+6, tblTextColor) -- dOffset + x+5,y+1 -- 7th - Bottom Line of Digit --octane.image.setPixel(imgCanvas, dOffset + x+1, y+7, tblTextColor) -- dOffset + x+0,y+7 octane.image.setPixel(imgCanvas, dOffset + x+2, y+7, tblTextColor) -- dOffset + x+4,y+7 octane.image.setPixel(imgCanvas, dOffset + x+3, y+7, tblTextColor) -- dOffset + x+3,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+4, y+7, tblTextColor) -- dOffset + x+4,y+1 --octane.image.setPixel(imgCanvas, dOffset + x+5, y+7, tblTextColor) -- dOffset + x+5,y+1 end dOffset = dOffset + iOffset end -- Update the image bmpComponent:updateProperties{image = imgCanvas} end -- Not Used function setCustom(bmpComponent) print(" ") print("@setCustom") -- sRGB luminance(Y) values -- These look to be the D65 unadapted W3 specification local rY = 0.212655 local gY = 0.715158 local bY = 0.072187 -- Process the Image print(" Processing Image...") local pixOriginalValue = {} local pixNewValue = {} print(" Image to sample:", bmpDiffuse.image) print(" Image to paint:" , bmpComponent.image) for x = 1, bmpComponent.width, 1 do for y = 1, bmpComponent.height, 1 do -- Get colors from original sRGB image pixOriginalValue = octane.image.getPixel(imgDIFLastRenderResult, x, y) -- 1) Greyscale result, Calculated per LBNL Radiance software --pixNewValue = (0.265 * pixOriginalValue[1]) + (0.670 * pixOriginalValue[2]) + (0.065 * pixOriginalValue[3]) -- Convert monochrome calculation for a color image --pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} -- 2) Greyscale Result, Calculated with different multipliers --pixNewValue = (rY * pixOriginalValue[1]) + (gY* pixOriginalValue[2]) + (bY * pixOriginalValue[3]) -- Convert monochrome calculation for a color image --pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} -- 3) Make greyscale sRGB with a function - Remove the gamma from the individual sRGB values, apply the multipliers and reapply the gamma to the resulting greyscale --pixNewValue = rgbBrightness(pixOriginalValue[1], pixOriginalValue[2], pixOriginalValue[3]) --pixNewValue = {pixNewValue, pixNewValue, pixNewValue, 255} -- 4) Make greyscale sRGB - Remove the gamma from the individual sRGB values, apply the multipliers and reapply the gamma to the resulting greyscale --pixNewValue = gam_sRGB( inv_gam_sRGB(pixOriginalValue[1]) * rY + inv_gam_sRGB(pixOriginalValue[2]) * gY + inv_gam_sRGB(pixOriginalValue[2]) * bY ) -- Convert above monochrome calculation for a color image type --pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} -- 4a) Make greyscale sRGB with a function - Remove the gamma from the individual sRGB values, apply the multipliers and reapply the gamma to the resulting greyscale --pixNewValue = gray(pixOriginalValue[1], pixOriginalValue[2], pixOriginalValue[3]) -- Convert above monochrome calculation for a color image type --pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} -- ********************************************************* -- 6) Make greyscale Linear - Remove the gamma from the sRGB value, DO apply the multipliers, DO NOT reapply the Gamma to the resulting linear RGB, -- DO convert back to 8bit rgb, this should now be a greyscale image with no tone mapping equivalent to a greyscale Octane Linear/off --pixNewValue = ( inv_gam_sRGB(pixOriginalValue[1] *rY *255) + inv_gam_sRGB(pixOriginalValue[2] *gY *255) + inv_gam_sRGB(pixOriginalValue[2] *bY *255)) -- Convert monochrome calculation for a color image --pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} -- 7) Assumes an sRGB input. Make colored Linear - Remove the gamma from the sRGB value, DO apply the radiance multipliers, DO NOT reapply the Gamma to the resulting linear RGB, -- DO convert back to 8bit rgb, this should now be an image with no tone mapping equivalent to a color Octane Linear/off -- Normalize and Remove the Gamma local nR = ((pixOriginalValue[1]/255) <= 0.04045 and (pixOriginalValue[1]/255)/12.92 or math.pow(((pixOriginalValue[1]/255)+0.055)/1.055, 2.4)) local nG = ((pixOriginalValue[2]/255) <= 0.04045 and (pixOriginalValue[2]/255)/12.92 or math.pow(((pixOriginalValue[2]/255)+0.055)/1.055, 2.4)) local nB = ((pixOriginalValue[3]/255) <= 0.04045 and (pixOriginalValue[3]/255)/12.92 or math.pow(((pixOriginalValue[3]/255)+0.055)/1.055, 2.4)) -- Using LBNL Radiance values, apply Multipliers and Convert back to sRGB local Lumens = (0.265*nR + 0.670*nG + 0.065*nB) * 255 -- Using NON Radiance values, apply Multipliers and Convert back to sRGB --local Lumens = (rY*nR + gY*nG + bY*nB) * 255 -- Convert monochrome calculation for use in a color image type pixNewValue = {Lumens, Lumens ,Lumens, 255} -- Gamma accuracy test --pixNewValue = { 255 * inv_gam_sRGB(pixOriginalValue[1]), 255 * inv_gam_sRGB(pixOriginalValue[2]), 255 * inv_gam_sRGB(pixOriginalValue[3]), 255} -- Reapply the Gamma, this should be equivalent to sRGB, or equivalent to Linear/off with a Gamma of 2.2 --pixNewValue = { gam_sRGB(inv_gam_sRGB(pixOriginalValue[1])), gam_sRGB(inv_gam_sRGB(pixOriginalValue[2])), gam_sRGB(inv_gam_sRGB(pixOriginalValue[2])), 255} -- Paint the image in the bitmap component, a grey scale version of Octane's Linear/off octane.image.setPixel(bmpComponent.image, x, y, pixNewValue) -- Backup the image in an unthemed state, this image is used in calculating all themeing -- and spot metering values regardless of the image being displayed as a backdrop. octane.image.setPixel(imgLuminance, x, y, pixNewValue) end end -- backup the image in an unthemed state --imgLuminance = bmpLuminance.image bmpComponent:updateProperties{} print(" Processing Complete...") --print(" ") print("Exiting setLuminance") end function setLuminance(bmpComponent) print(" ") print("@setLuminance") -- Process the Image print(" Processing Image...") local pixOriginalValue = {} local pixNewValue = {} local tblDIFRenderResults = octane.render.grabRenderResult() imgDIFLastRenderResult = tblDIFRenderResults[1].image calcLumens() print(" Image to sample:", imgDIFLastRenderResult) print(" Image to paint:" , bmpComponent.image) for x = 1, bmpComponent.width, 1 do for y = 1, bmpComponent.height, 1 do -- Get colors from original sRGB image pixOriginalValue = octane.image.getPixel(imgDIFLastRenderResult, x, y) -- Normalize and Remove the Gamma local nR = ((pixOriginalValue[1]/255) <= 0.04045 and (pixOriginalValue[1]/255)/12.92 or math.pow(((pixOriginalValue[1]/255)+0.055)/1.055, 2.4)) local nG = ((pixOriginalValue[2]/255) <= 0.04045 and (pixOriginalValue[2]/255)/12.92 or math.pow(((pixOriginalValue[2]/255)+0.055)/1.055, 2.4)) local nB = ((pixOriginalValue[3]/255) <= 0.04045 and (pixOriginalValue[3]/255)/12.92 or math.pow(((pixOriginalValue[3]/255)+0.055)/1.055, 2.4)) -- Using LBNL Radiance values, apply Multipliers and denormalize local Lumens = (0.265 * nR + 0.670 * nG + 0.065 * nB) * 255 -- Convert monochrome calculation for use in a color image type pixNewValue = {Lumens, Lumens ,Lumens, 255} -- Paint the image in the bitmap component, a greyscale version of Octane's Linear/off octane.image.setPixel(bmpComponent.image, x, y, pixNewValue) -- Backup the image in an unthemed state, this image is used in calculating all theming -- and spot metering values regardless of the image being displayed as a backdrop. octane.image.setPixel(imgLuminance, x, y, pixNewValue) end end bmpComponent:updateProperties{} print(" Processing Complete...") print("Exiting setLuminance") end function setStandardRGB(bmpComponent) -- Converts an sRGB image to Greyscale print(" ") print("@setStandardRGB") -- sRGB luminance(Y) values local rY = 0.212655 local gY = 0.715158 local bY = 0.072187 -- Process the Image print(" Processing Image...") local pixOriginalValue = {} local pixNewValue = {} local tblDIFRenderResults = octane.render.grabRenderResult() imgDIFLastRenderResult = tblDIFRenderResults[1].image print(" Image to sample:", imgDIFLastRenderResult.image) print(" Image to paint:", bmpComponent.image) for x = 1, bmpComponent.width, 1 do for y = 1, bmpComponent.height, 1 do -- This is the luminance value of the original diffuse Map calculated per LBNL Radiance software -- 179 lm/w * (0.265*R + 0.670*G + 0.065*B) -- https://discourse.radiance-online.org/t/luminous-efficacy/1400 -- Get colors from original sRGB image pixOriginalValue = octane.image.getPixel(imgDIFLastRenderResult, x, y) -- Non linear conversion of sRGB channel values, Standard RGB equation --pixNewValue = (rY * pixOriginalValue[1]) + (gY* pixOriginalValue[2]) + (bY * pixOriginalValue[3]) -- Non linear conversion of monochrome calculation for a color image --pixNewValue = {pixNewValue, pixNewValue, pixNewValue, 255} -- Linear conversion of sRGB channel values, Standard RGB equation pixNewValue = (rY * (pixOriginalValue[1]/255)) + (gY * (pixOriginalValue[2]/255)) + (bY * (pixOriginalValue[3]/255)) -- Linear conversion of monochrome calculation for a color image type pixNewValue = {pixNewValue * 255, pixNewValue * 255, pixNewValue * 255, 255} -- Paint the pixels in the bitmap octane.image.setPixel(bmpComponent.image, x, y, pixNewValue) end end bmpComponent:updateProperties{} print(" Processing Complete...") print("Exiting setStandardRGB") end -- Not Used function setRadianceRGB(bmpComponent) print(" ") print("@setCustom") -- Process the Image print(" Processing Image...") local pixOriginalValue = {} local pixNewValue = {} print(" Image to sample:", bmpDiffuse.image) print(" Image to paint:", bmpComponent.image) for x = 1, bmpComponent.width, 1 do for y = 1, bmpComponent.height, 1 do -- This is the luminance value of the original diffuse Map calculated per LBNL Radiance software -- 179 lm/w * (0.265*R + 0.670*G + 0.065*B) -- https://discourse.radiance-online.org/t/luminous-efficacy/1400 pixOriginalValue = octane.image.getPixel(imgDIFLastRenderResult, x, y) pixNewValue = (0.265 * pixOriginalValue[1]) + (0.670 * pixOriginalValue[2]) + (0.065 * pixOriginalValue[3]) --pixNewValue = (0.265 * pixOriginalValue[1]/255) + (0.670 * pixOriginalValue[2]/255) + (0.065 * pixOriginalValue[3]/255) -- Convert monochrome calculation for a color image pixNewValue = {pixNewValue ,pixNewValue ,pixNewValue ,255} octane.image.setPixel(bmpComponent.image, x, y, pixNewValue) end end bmpComponent:updateProperties{} print(" Processing Complete...") print("Exiting setLuminance") end ---------------------------------------------------------------------------------------------------------------- -- Main Routine ------------------------------------------------------------------------------------------------ ---------------------------------------------------------------------------------------------------------------- --Get info about current rendering --local tblLastRenderInfo = octane.render.grabRenderResult() --tprint(tblLastRenderInfo) --[[ -- choose an input file local ret1 = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, path = octane.file.getParentDirectory(octane.project.getCurrentProject()), title = "Select Image File", wildcards = "*.jpg;*.png", save = false, } -- if a file is chosen if ret1.result ~= "" then strFilePath = ret1.result print(strFilePath) local exists = octane.file.isFile(strFilePath) if exists == false then print("File does not exist") showWarning("File failed to open.", "Please select a suitable image file on which to base the material.") return else print("File does exist") print(strFilePath) -- Setup input image --local imgDiffuse = octane.image.load(strFilePath, 0) --local nodeImageTexture = getLastRenderResult(self, graph) --]] -- Make and place on graph, an RGBA texture node with the latest rending inside. --getLastRenderResult() -- Setup Diffuse Image from Render Target's last rendering -- This image is declared as a global at beginning of the script local tblDIFRenderResults = octane.render.grabRenderResult() tprint(tblDIFRenderResults) imgDIFLastRenderResult = tblDIFRenderResults[1].image PROPS_IMAGE_DIF = octane.image.getProperties(imgDIFLastRenderResult) bmpDiffuse = octane.gui.create { type = octane.gui.componentType.BITMAP, width = PROPS_IMAGE_DIF.size[1], height = PROPS_IMAGE_DIF.size[2], opacity = 1, backgroundColour = { 255, 255, 255, 0 }, mouseCallback = mouseCallback, image = imgDIFLastRenderResult } lblDiffuseImage.text = "Original Image: Rendertarget Output, ".. PROPS_IMAGE_DIF["size"][1] .. " x " .. PROPS_IMAGE_DIF["size"][2] .. " pixels" -- Setup Luminance image to match diffuse -- This image (imgLuminance) is declared as a global at beginning of the script, is populated -- by the setLuminance function and used as the clean source of luminance values when -- themeing and labeling the 3 bitmap images. local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image PROPS_IMAGE_LUM = octane.image.getProperties(imgLUMLastRenderResult) imgLuminance = octane.image.create(PROPS_IMAGE_LUM) bmpLuminance = octane.gui.create { type = octane.gui.componentType.BITMAP, width = PROPS_IMAGE_LUM.size[1], height = PROPS_IMAGE_LUM.size[2], opacity = 1, backgroundColour = { 255, 255, 255, 0 }, mouseCallback = mouseCallback, image = imgLUMLastRenderResult } setLuminance(bmpLuminance) -- Setup False Color Image to match --local imgFalseColor = octane.image.load(strFilePath, 1) local tblFCRenderResults = octane.render.grabRenderResult() imgFCLastRenderResult = tblFCRenderResults[1].image PROPS_IMAGE_FC = octane.image.getProperties(imgFCLastRenderResult) bmpFalseColor = octane.gui.create { type = octane.gui.componentType.BITMAP, width = PROPS_IMAGE_FC.size[1], height = PROPS_IMAGE_FC.size[2], opacity = 1, backgroundColour = { 255, 255, 255, 0 }, mouseCallback = mouseCallback, image = imgFCLastRenderResult } -- Enable GUI components lblInputPath:updateProperties {text = strFilePath} lblInputPath:updateProperties {tooltip = strFilePath} btnFileSave:updateProperties {enable = true} octane.gui.dispatchGuiEvents(50) -- Assign an image to the two bitmaps so we can use them later. local imgLegendCanvas = octane.image.create({type = 0, size = {bmpLegend.width, bmpLegend.height}}) bmpLegend:updateProperties{image = imgLegendCanvas} -- This function uses lerp to grade colors which only processes 3-component vectors -- If an alpha channel is supplied, the function strips it, hence alpha is not necessary but can be present drawGradient( bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd ) local imgFalseColorHistogramCanvas = octane.image.create({type = 0, size = {bmpFalseColorHistogram.width, bmpFalseColorHistogram.height}}) bmpFalseColorHistogram:updateProperties{image = imgFalseColorHistogramCanvas} ---------------------------------------------------------------------------------------------------------------- -- End Main Routine ---------------------------------------------------------------------------------------------------------------- -- Update Analytic groups to include new bitmaps bmpLegend:updateProperties { backgroundColour = {127,127,127} } grpAnalyticsTab1:updateProperties { children = { lblDiffuseImage, bmpDiffuse, lblSpotMeterDiffuse, grpAnalyticsTab1a } } grpAnalyticsTab2:updateProperties { children = { lblLuminanceImage, bmpLuminance, lblSpotMeterLuminanceValue, grpAnalyticsTab2a } } grpAnalyticsTab3:updateProperties { children = { lblFalseColorImage, bmpFalseColor, lblLegendMeterFalseColor, grpAnalyticsTab3a, bmpFalseColorHistogram } } tabImages:updateProperties { children = { grpAnalyticsTab1, grpAnalyticsTab2, grpAnalyticsTab3, grpAnalyticsTab4 }, currentTab = 2} -- Build Main Window, must be done after Main Routine has created the bitmap local grpAll = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 2, cols = 1, width = bmpDiffuse.width + 30, children = { tabImages, grpOutput }, --children = { grpInput, tabImages, grpOutput }, border = false, inset = { 5 }, padding = { 5 }, } local wndMain = octane.gui.create { type = octane.gui.componentType.WINDOW, children = { grpAll }, width = grpAll:getProperties().width -18, height = grpAll:getProperties().height, text = "Image Luminance Analysis: ( ".. bmpDiffuse.width .." x ".. bmpDiffuse.height .." )" } function mouseCallback(component, event, pX, pY, wX, wY) if component == bmpFalseColorHistogram then -- Bounds check if pX > bmpFalseColorHistogram.width or pX < 1 then return end if pY > bmpFalseColorHistogram.height or pY < 1 then return end if event == octane.gui.eventType.MOUSE_DOWN then if chkIlluminanceUnitLux.checked == true then lblLegendMeterFalseColor:updateProperties{text = tostring(math.ceil(pX * 45645/bmpFalseColorHistogram.width))} else lblLegendMeterFalseColor:updateProperties{text = tostring(math.ceil(pX * 45645/10.752/bmpFalseColorHistogram.width))} end end if event == octane.gui.eventType.MOUSE_UP then lblLegendMeterFalseColor:updateProperties{text = "Legend Value:"} end end if component == bmpLegend then -- Bounds check if pX > bmpLegend.width or pX < 1 then return end if pY > bmpLegend.height or pY < 1 then return end if event == octane.gui.eventType.MOUSE_DOWN then if chkIlluminanceUnitLux.checked == true then lblLegendMeterFalseColor:updateProperties{text = tostring(math.ceil(pX * 45645/bmpLegend.width))} else lblLegendMeterFalseColor:updateProperties{text = tostring(math.ceil(pX * 45645/10.752/bmpLegend.width))} end end if event == octane.gui.eventType.MOUSE_UP then lblLegendMeterFalseColor:updateProperties{text = "Legend Value:"} end end if component == bmpLuminance then -- Bounds check if pX > bmpLuminance.width or pX < 1 then return end if pY > bmpLuminance.height or pY < 1 then return end if event == octane.gui.eventType.MOUSE_DRAG then print("@bmpLuminance mouse_drag event") local tblPixelValue = {} -- Color image, therefore returns a table of values tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLux = 179 * (tblPixelValue[1]) print(" Color = ", tblPixelValue[1] ..",".. tblPixelValue[2] ..",".. tblPixelValue[3] ..",".. tblPixelValue[4]) print(" Luminance value = ", numLux ) if chkIlluminanceUnitLux.checked == true then lblSpotMeterLuminanceValue:updateProperties{text = tostring(math.ceil(numLux)) .." lux"} else lblSpotMeterLuminanceValue:updateProperties{text = tostring(math.ceil(numLux/10.752)) .." fc"} end end if event == octane.gui.eventType.MOUSE_UP then --or event == octane.gui.eventType.MOUSE_DRAG then -- Bounds check if pX > bmpLuminance.width or pX < 1 then return end if pY > bmpLuminance.height or pY < 1 then return end -- Spot metering should not use theme colors - if on turn it off local blnThemeSamplingWasTrue = false if chkThemeSamplingGrid.checked == true then chkThemeSamplingGrid.checked = false blnThemeSamplingWasTrue = true end print(" ") print("@bmpLuminance mouse_up event") if chkSpotMeterLabelPoints.checked == true then -- Color image, therefore returns a table of values local tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLux = 179 * tblPixelValue[1] print(" Value of numLux from Pixel's red channel ", numLux) print(" at location", pX,pY ) if chkIlluminanceUnitFootCandle.checked == true then lblSpotMeterLuminanceValue:updateProperties{text = "Spot Meter Value: ".. tostring(math.ceil(numLux/10.752)) .." fc"} else lblSpotMeterLuminanceValue:updateProperties{text = "Spot Meter Value: ".. tostring(math.ceil(numLux)) .." lux"} end -- Mark the spot on the luminance image, -- luminance calculations will be performed inside the function drawGridTic(bmpLuminance, pX, pY, clrSpotMeterTic, clrSpotMeterText, 10 ) end if blnThemeSamplingWasTrue == true then chkThemeSamplingGrid.checked = true end end end if component == bmpDiffuse then if event == octane.gui.eventType.MOUSE_DRAG then -- Bounds check if pX > bmpDiffuse.width or pX < 1 then return end if pY > bmpDiffuse.height or pY < 1 then return end print("@bmpDiffuse mouse_drag event") local tblPixelValue = {} -- Color image, therefore returns a table of values tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLux = 179 * (tblPixelValue[1]) print(" Color = ", tblPixelValue[1] ..",".. tblPixelValue[2] ..",".. tblPixelValue[3] ..",".. tblPixelValue[4]) print(" Luminance value = ", numLux ) if chkIlluminanceUnitLux.checked == true then lblSpotMeterDiffuse:updateProperties{text = tostring(math.ceil(numLux)) .." lux"} else lblSpotMeterDiffuse:updateProperties{text = tostring(math.ceil(numLux/10.752)) .." fc"} end end if event == octane.gui.eventType.MOUSE_UP then --or event == octane.gui.eventType.MOUSE_DRAG then -- Spot metering should not use theme colors - if on turn it off local blnThemeSamplingWasTrue = false if chkThemeSamplingGrid.checked == true then chkThemeSamplingGrid.checked = false blnThemeSamplingWasTrue = true end -- Bounds check if pX > bmpLuminance.width or pX < 1 then return end if pY > bmpLuminance.height or pY < 1 then return end print(" ") print("@bmpDiffuse mouse_up event") if chkSpotMeterLabelPoints.checked == true then -- Color image, therefore returns a table of values local tblPixelValue = octane.image.getPixel(imgLuminance, pX, pY) local numLux = 179 * tblPixelValue[1] print(" Value of numLux from Pixel's red channel ", numLux) print(" at location", pX,pY ) if chkIlluminanceUnitFootCandle.checked == true then lblSpotMeterDiffuse:updateProperties{text = tostring(math.ceil(numLux/10.752)) .." fc"} else lblSpotMeterDiffuse:updateProperties{text = tostring(math.ceil(numLux)) .." lux"} end -- Mark the spot on the luminance image, -- luminance calculations will be performed inside the function drawGridTic(bmpDiffuse, pX, pY, clrSpotMeterTic, clrSpotMeterText, 10 ) end if blnThemeSamplingWasTrue == true then chkThemeSamplingGrid.checked = true end end end end function guiCallback(component, event) --[[ if component == xbtnFileOpen then -- choose an input file local ret1 = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, path = octane.file.getParentDirectory(octane.project.getCurrentProject()), title = "Select Image File", wildcards = "*.jpg;*.png", save = false, } -- if a file is chosen if ret1.result ~= "" then strFilePath = ret1.result print(strFilePath) local exists = octane.file.isFile(strFilePath) if exists == false then print("File does not exist") showWarning("File failed to open.", "Please select a suitable image file on which to base the material.") --btnProcessFile:updateProperties { enable = false } return else print("File does exist") local imgDiffuse = octane.image.load(strFilePath, 0) PROPS_IMAGE = octane.image.getProperties(imgTemp) tprint(PROPS_IMAGE) -- Resize bitmap the same size as the selected image texture so we can display it local bmpDiffuse = octane.gui.create { type = octane.gui.componentType.BITMAP, width = PROPS_IMAGE.size[1], height = PROPS_IMAGE.size[2], opacity = 1, backgroundColour = { 255, 255, 255, 0 }, mouseCallback = mouseCallback, image = imgDiffuse } strDIFF_FilePath = strFilePath --btnProcessFile:updateProperties { enable = true } end -- Enable GUI components lblInputPath:updateProperties {text = strFilePath} lblInputPath:updateProperties {tooltip = strFilePath} bmpDiffuse:updateProperties{image = octane.image.load(strFilePath,0)} btnFileSave:updateProperties{enable = true} octane.gui.dispatchGuiEvents(50) --lblDiffuseMap.text = "Diffuse Map (source) ".. PROPS_IMAGE["size"][1] .. " x " .. PROPS_IMAGE["size"][2] .. " pixels" else --btnProcessFile:updateProperties{enabled = false } strFilePath = nil end end --]] if component == btnDrawGridOnDiffuse then drawGrid(bmpDiffuse, numSamplingGridStepX, numSamplingGridStepY, clrSamplingGridTic, clrSamplingGridText, numTicSize) end if component == btnResetDiffuse then print("@btnResetDiffuse") -- Setup Diffuse Image from Render Target's last rendering local tblDIFRenderResults = octane.render.grabRenderResult() imgDIFLastRenderResult = tblDIFRenderResults[1].image --PROPS_IMAGE_DIF = octane.image.getProperties(imgDIFLastRenderResult) --local imgDiffuse = octane.image.create(PROPS_IMAGE_DIF) imgDiffuse = imgDIFLastRenderResult bmpDiffuse:updateProperties{image = imgDiffuse} end if component == btnDrawGridOnLuminance then drawGrid(bmpLuminance, numSamplingGridStepX, numSamplingGridStepY, clrSamplingGridTic, clrSamplingGridText, numTicSize) end if component == btnResetLuminance then print(" ") print("@btnResetLuminance") if chkToneMappingLinear.checked == true then -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setLuminance(bmpLuminance) end if chkToneMappingRGB.checked == true then -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setStandardRGB(bmpLuminance) end end if component == chkToneMappingLinear then print(" ") print("@chkToneMappingLinear") if chkToneMappingLinear.checked == true then chkToneMappingRGB.checked = false -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setLuminance(bmpLuminance) else chkToneMappingRGB.checked = true -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setStandardRGB(bmpLuminance) end lblLuminanceImage.text = "Luminance Image: Greyscale, Response curve is equivalent to Linear/off." lblSpotMeterLuminanceValue:updateProperties{text = "Spot Meter Value: 0.0"} end if component == chkToneMappingRGB then print(" ") print("@chkToneMappingRGB") if chkToneMappingRGB.checked == true then chkToneMappingLinear.checked = false -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setStandardRGB(bmpLuminance) else chkToneMappingLinear.checked = true -- Setup Luminance Image from Render Target's last rendering local tblLUMRenderResults = octane.render.grabRenderResult() imgLUMLastRenderResult = tblLUMRenderResults[1].image setLuminance(bmpLuminance) end lblLuminanceImage.text = "Luminance Image: Greyscale, Response curve is equivalent to sRGB." lblSpotMeterLuminanceValue:updateProperties{text = "Spot Meter Value: 0.0"} end if component == btnDrawLegend then -- Global Settings print("Legend Image dimensions = ", bmpLegend.width .." x ".. bmpLegend.height) print("numLegendOffsetX =", numLegendOffsetX) print("numLegendOffsetY =", numLegendOffsetY) print(" ") -- Setup Legend Text local numLegendTextOffsetX = numLegendOffsetX local numLegendTextOffsetY = bmpFalseColor.height - numLegendOffsetY + (bmpLegend.height + 3) print("Text location on bmpDiffuse") print("numLegendTextOffsetX =", numLegendTextOffsetX) print("numLegendTextOffsetY =", numLegendTextOffsetY) if tabImages:getProperties().currentTab == 1 then -- Diffuse Image octane.image.copyRegion(bmpDiffuse.image, bmpLegend.image, { 1, 1, bmpLegend.width, bmpLegend.height }, {numLegendOffsetX, bmpDiffuse.height - numLegendOffsetY}, true) tblTextColor = {127,127,127,255} drawDigit(0, bmpDiffuse, numLegendTextOffsetX + 0, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(22500, bmpDiffuse, numLegendTextOffsetX + ( bmpLegend.width/2) - 15, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(45645, bmpDiffuse, numLegendTextOffsetX + bmpLegend.width - 30, numLegendTextOffsetY, tblTextColor, 0, 0 ) elseif tabImages:getProperties().currentTab == 2 then -- Luminance Image octane.image.copyRegion(bmpLuminance.image, bmpLegend.image, { 1, 1, bmpLegend.width, bmpLegend.height }, {numLegendOffsetX, bmpLuminance.height - numLegendOffsetY}, true) tblTextColor = {127,127,127,255} drawDigit(0, bmpLuminance, numLegendTextOffsetX + 0, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(22500, bmpLuminance, numLegendTextOffsetX + ( bmpLegend.width/2) - 15, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(45645, bmpLuminance, numLegendTextOffsetX + bmpLegend.width - 30, numLegendTextOffsetY, tblTextColor, 0, 0 ) elseif tabImages:getProperties().currentTab == 3 then -- False Color Image octane.image.copyRegion(bmpFalseColor.image, bmpLegend.image, { 1, 1, bmpLegend.width, bmpLegend.height }, {numLegendOffsetX, bmpFalseColor.height - numLegendOffsetY}, true) tblTextColor = {127,127,127,255} drawDigit(0, bmpFalseColor, numLegendTextOffsetX + 0, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(22500, bmpFalseColor, numLegendTextOffsetX + ( bmpLegend.width/2) - 15, numLegendTextOffsetY, tblTextColor, 0, 0 ) drawDigit(45645, bmpFalseColor, numLegendTextOffsetX + bmpLegend.width - 30, numLegendTextOffsetY, tblTextColor, 0, 0 ) bmpFalseColor:updateProperties{} end end if component == btnThemeImage then print(" ") print("@btnThemeImage") print(" Value of inputs to function 'themeImage' ", bmpFalseColor, numInitialValue, numLumensPerTheme, numThemeCount) themeImage(bmpFalseColor, 0, numLumensPerTheme, numThemeCount) print(" ") print("Exiting btnThemeImage") end if component == btnResetFalseColorImage then print(" ") print("@btnResetFalseColorImage") local tblFCRenderResults = octane.render.grabRenderResult() imgFCLastRenderResult = tblFCRenderResults[1].image bmpFalseColor:updateProperties{image = imgFCLastRenderResult} print("Exiting btnResetFalseColorImage") end if component == btnFileSave then -- Choose an input file local rv = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG , path = octane.file.getParentDirectory(octane.project.getCurrentProject()), title = "Save As", wildcards = "*.jpg;*.png", save = True, } tprint(rv) if tabImages:getProperties().currentTab == 1 then rv = octane.image.save(bmpDiffuse.image, rv.result..".png") elseif tabImages:getProperties().currentTab == 2 then rv = octane.image.save(bmpLuminance.image, rv.result..".png") elseif tabImages:getProperties().currentTab == 3 then rv = octane.image.save(bmpFalseColor.image, rv.result..".png") end end if component == chkIlluminanceUnitFootCandle then if chkIlluminanceUnitFootCandle.checked == true then chkIlluminanceUnitLux.checked = false else chkIlluminanceUnitLux.checked = true end end if component == chkIlluminanceUnitLux then if chkIlluminanceUnitLux.checked == true then chkIlluminanceUnitFootCandle.checked = false else chkIlluminanceUnitFootCandle.checked = true end end if component == chkThemeSamplingGrid then if chkThemeSamplingGrid.checked == true then blnThemeSamplingGrid = false else blnThemeSamplingGrid = true end end if component == clsLegendStartColorPicker then clrLegendStart = clsLegendStartColorPicker.colour drawGradient(bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd) --bmpLegend:updateProperties{} end if component == clsLegendMiddleColorPicker then clrLegendMiddle = clsLegendMiddleColorPicker.colour drawGradient(bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd) --bmpLegend:updateProperties{} end if component == clsLegendEndColorPicker then clrLegendEnd = clsLegendEndColorPicker.colour -- This call updates tblFalseColors drawGradient(bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd) end if component == nbxLumensPerTheme then numLumensPerTheme = nbxLumensPerTheme.value numThemeCount = math.floor((numMaxLuminance/numLumensPerTheme) + .5) nbxThemeCount:updateProperties{value = numThemeCount} -- Set up tblFalseColors with correct number of themes by drawing the gradient drawGradient(bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd) print("@nbxLumensPerTheme") print(" numLumensPerTheme = ", numLumensPerTheme) print(" numThemeCount = ", numThemeCount) end if component == nbxThemeCount then print("@nbxThemeCount") numThemeCount = nbxThemeCount.value print(" Value of numThemeCount = ", numThemeCount) numLumensPerTheme = math.floor(numMaxLuminance/numThemeCount + .5) print(" Value of numLumensPerTheme = ", numLumensPerTheme) nbxLumensPerTheme:updateProperties{value = numLumensPerTheme} -- Set up tblFalseColors with correct number of themes by drawing the gradient drawGradient(bmpLegend, clrLegendStart, clrLegendMiddle, clrLegendEnd) print("Exiting nbxThemeCount") end if component == clsSampleGridTicColor then clrSamplingGridTic = clsSampleGridTicColor.colour end if component == clsSampleGridTextColor then clrSamplingGridText = clsSampleGridTextColor.colour end if component == clsSpotMeterTicColor then clrSpotMeterTic = clsSpotMeterTicColor.colour end if component == clsSpotMeterTextColor then clrSpotMeterText = clsSpotMeterTextColor.colour end if component == nbxGridStepX then numSamplingGridStepX = nbxGridStepX .value end if component == nbxGridStepY then numSamplingGridStepY = nbxGridStepY .value end if component == btnHelpOpen then txtHelp.text = strHelpText wndHelp:showWindow() end if component == btnHelpClose then wndHelp:closeWindow() end if component == btnMainClose then print(" ") print("Application terminated by user") wndMain:closeWindow() end end -- Hookup bitmaps to mouse events bmpDiffuse:updateProperties {mouseCallback = mouseCallback } bmpLuminance:updateProperties {mouseCallback = mouseCallback } bmpFalseColor:updateProperties {mouseCallback = mouseCallback } bmpFalseColorHistogram:updateProperties {mouseCallback = mouseCallback } bmpLegend:updateProperties {mouseCallback = mouseCallback } -- Hookup Luminance Controls btnDrawGridOnDiffuse:updateProperties { callback = guiCallback } btnResetDiffuse:updateProperties { callback = guiCallback } btnDrawGridOnLuminance:updateProperties { callback = guiCallback } btnResetLuminance:updateProperties { callback = guiCallback } chkToneMappingRGB:updateProperties { callback = guiCallback } chkToneMappingLinear:updateProperties { callback = guiCallback } -- Hookup False Color Controls nbxLegendLeftOffset:updateProperties { callback = function(component, event) numLegendOffsetX = nbxLegendLeftOffset.value end} nbxLegendBottomOffset:updateProperties { callback = function(component, event) numLegendOffsetY = nbxLegendLeftOffset.value end} clsLegendStartColorPicker:updateProperties { callback = guiCallback } clsLegendMiddleColorPicker:updateProperties { callback = guiCallback } clsLegendEndColorPicker:updateProperties { callback = guiCallback } nbxLumensPerTheme:updateProperties { callback = guiCallback } nbxThemeCount:updateProperties { callback = guiCallback } btnResetFalseColorImage:updateProperties { callback = guiCallback } -- Hookup Sampling Grid Controls nbxGridStepY:updateProperties { callback = guiCallback } nbxGridStepX:updateProperties { callback = guiCallback } clsSampleGridTicColor:updateProperties { callback = guiCallback } clsSampleGridTextColor:updateProperties { callback = guiCallback } chkThemeSamplingGrid:updateProperties { callback = guiCallback } -- Hookup Spot Meter Controls chkSpotMeterLabelPoints:updateProperties { callback = guiCallback } clsSpotMeterTicColor:updateProperties { callback = guiCallback } clsSpotMeterTextColor:updateProperties { callback = guiCallback } -- Hookup units of measure chkIlluminanceUnitLux:updateProperties { callback = guiCallback } chkIlluminanceUnitFootCandle:updateProperties { callback = guiCallback } btnFileOpen:updateProperties { callback = guiCallback } btnDrawLegend:updateProperties { callback = guiCallback } btnThemeImage:updateProperties { callback = guiCallback } btnHelpOpen:updateProperties { callback = guiCallback } btnHelpClose:updateProperties { callback = guiCallback } btnFileSave:updateProperties { callback = guiCallback } btnMainClose:updateProperties { callback = guiCallback } --Display the GUI wndMain:showWindow() --[[ ---------------------------------------------------------------------------------------------------------------- -- Code snippet holding Pen. ---------------------------------------------------------------------------------------------------------------- -- reverses the rgb gamma function inverseGamma(t) return (t <= 0.0404482362771076) and (t/12.92) or ((t + 0.055)/1.055)^2.4 end -- CIE L*a*b* f function (used to convert XYZ to L*a*b*) http://en.wikipedia.org/wiki/Lab_color_space function LABF(t) return (t >= 8.85645167903563082e-3) and t^(1/3) or (841.0/108.0)*t + (4.0/29.0) end function rgbToCIEL(p) local y local r = p.r/255.0 local g = p.g/255.0 local b = p.b/255.0 r = inverseGamma(r) g = inverseGamma(g) b = inverseGamma(b) -- Observer = 2°, Illuminant = D65 y = 0.2125862307855955516*r + 0.7151703037034108499*g + 0.07220049864333622685*b -- At this point we've done RGBtoXYZ now do XYZ to Lab -- y /= WHITEPOINT_Y; The white point for y in D65 is 1.0 y = LABF(y) -- This is the "normal conversion which produces values scaled to 100 -- Lab.L = 116.0*y - 16.0; return 1.16*y - 0.16 -- return values for 0.0 >=L <=1.0 end -- sRGB luminance(Y) values local rY = 0.212655 local gY = 0.715158 local bY = 0.072187 -- Inverse of sRGB "gamma" function. (approx 2.2) local function inv_gam_sRGB(ic) local c = ic/255.0 if c <= 0.04045 then return c/12.92 else return ((c+0.055)/(1.055))^2.4 end end -- sRGB "gamma" function (approx 2.2) local function gam_sRGB(v) if v <= 0.0031308 then v = v * 12.92 else v = 1.055 * v^(1.0/2.4) - 0.055 end return math.floor(v*255 + 0.5) -- This is correct in Lua end -- GRAY VALUE ("brightness") local function gray(r, g, b) return gam_sRGB( rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b)) end -- Set up a canvas based on an existing bit map control. -- First setup an empty image to paint on and subsequently assign it to the bitmap component --local PROPS_IMAGE = {type = 0, size = {bmpComponent.width, bmpComponent.height}} --tprint(PROPS_IMAGE) -- Create the new Image --local imgCanvas = octane.image.create(PROPS_IMAGE) --print(" Dimensions of imgCanvas = ", imgCanvas.size[1] .." x ".. imgCanvas.size[2]) -- Possible preset legend colors -- Magenta - Green - Yellow: --clrStart = { 255, 255, 0, 255 } -- Magenta --clrMiddle = { 0, 255, 0, 255 } -- Green --clrEnd = { 0, 0, 255, 255 } -- Blue -- Red - Green/Blue - Yellow: --clrStart = { 75, 0, 0, 255 } -- Dark red --clrMiddle = { 255, 125, 75, 255 } -- Green Blue --clrEnd = { 255, 255, 155, 255 } -- Yellow -- Dark Blue - Green/Blue - Yellow: --clrStart = { 0, 0, 100, 255 } -- Dark Blue --clrMiddle = { 0, 200, 200, 255 } -- Green Blue --clrEnd = { 255, 255, 155, 255 } -- Yellow -- Alternative brightness/luminance calculations --local numLuminance = 179 * ((0.33 * tblPixelValue[1]) + (0.33 * tblPixelValue[2]) + (0.33 * tblPixelValue[3])) --local numLuminance = 179 * ((0.33 * tblPixelValue[1]) + (0.5 * tblPixelValue[2]) + (0.16 * tblPixelValue[3])) --local numLuminance = 179 * (0.265 * tblPixelValue[1] + 0.670 * tblPixelValue[2] + 0.065 * tblPixelValue[3]) --local numLuminance = 179 *((0.299 * tblPixelValue[1]) + ( 0.587 * tblPixelValue[2]) + (0.114 * tblPixelValue[3])) --local numLuminance = math.sqrt((math.ceil(0.299 * tblPixelValue[1]^2) + math.ceil( 0.587 * tblPixelValue[2]^2) + math.ceil(0.114 * tblPixelValue[3]^2))) -- Failed attempt to move the Label Component with the mouse --lblSpotMeterLuminanceValue:updateProperties{ x = pX, y = pY } --print (" Label x,y ", lblSpotMeterLuminanceValue.x, lblSpotMeterLuminanceValue.x) --lblSpotMeterLuminanceValue:updateProperties{} --grpAnalyticsTab2a:updateProperties{} --]]