How can I increase the performance of changing background color of cells?

I’m trying to draw a picture using cell background color, I choose beanshell to do this.
After studied how to change CellBackColor from here, I wrote a performance test script, and run it, and the performance is not good, is there a way that I can increase the performance?

Test result

// -----------------------------------------------------------------------------
// Output on a {CPU: AMD Ryzen 5 2400G,    Memory: 32GB,    OS: Fedora 41,    LibreOffice: 25.2.2.2} computer.
// -----------------------------------------------------------------------------
time cost of changing CellBackColor of 10x10 100 cells = 895 ms.
time cost of changing CellBackColor of 10x10 100 cells = 762 ms.
time cost of changing CellBackColor of 10x10 100 cells = 792 ms.
time cost of changing CellBackColor of 10x10 100 cells = 761 ms.
time cost of changing CellBackColor of 10x10 100 cells = 605 ms.
time cost of changing CellBackColor of 10x10 100 cells = 709 ms.
time cost of changing CellBackColor of 10x10 100 cells = 643 ms.
time cost of changing CellBackColor of 10x10 100 cells = 739 ms.

time cost of changing CellBackColor of 50x20 1000 cells = 4132 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4636 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4731 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4885 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 5031 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4450 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4609 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4617 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4785 ms.
time cost of changing CellBackColor of 50x20 1000 cells = 4886 ms.

time cost of changing CellBackColor of 20x50 1000 cells = 6865 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7759 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7692 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7235 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7821 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7282 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7529 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7357 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7168 ms.
time cost of changing CellBackColor of 20x50 1000 cells = 7556 ms.

Test script

import com.sun.star.uno.*;
import com.sun.star.beans.XPropertySet;
import com.sun.star.sheet.*;
import com.sun.star.table.*;

model = XSCRIPTCONTEXT.getDocument ();
controller = model.getCurrentController ();
view = UnoRuntime.queryInterface (XSpreadsheetView.class, controller);
sheet = view.getActiveSheet ();

Thread threadTest = new Thread ()
{
	public void run ()
	{
		try
		{
			//int nWidth = 10, nHeight = 10;
			//int nWidth = 50, nHeight = 20;
			int nWidth = 20, nHeight = 50;
			long nTimestamp_Start = System.currentTimeMillis ();
			for (int iRow=0; iRow<nHeight; iRow++)
			{
				for (int iCol=0; iCol<nWidth; iCol++)
				{
					cell = sheet.getCellByPosition (iCol, iRow);
					XPropertySet cell_prop_set = UnoRuntime.queryInterface (XPropertySet.class, cell);
					int nRGB = Math.random () * 0xFFFFFF;
					cell_prop_set.setPropertyValue ("CellBackColor", nRGB);
				}
			}
			long nTimestamp_End = System.currentTimeMillis ();
			long nDurationInMillisecond = nTimestamp_End - nTimestamp_Start;
			//long nDurationInSecond = nDurationInMillisecond / 1000;
System.out.println ("time cost of changing CellBackColor of " + nWidth + "x" + nHeight + " " + (nWidth*nHeight) + " cells = " + nDurationInMillisecond + " ms.");
// -----------------------------------------------------------------------------
// Output on a {CPU: AMD Ryzen 5 2400G,    Memory: 32GB,    OS: Fedora 41,    LibreOffice: 25.2.2.2} computer.
// -----------------------------------------------------------------------------
//time cost of changing CellBackColor of 10x10 100 cells = 895 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 762 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 792 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 761 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 605 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 709 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 643 ms.
//time cost of changing CellBackColor of 10x10 100 cells = 739 ms.
//
//time cost of changing CellBackColor of 50x20 1000 cells = 4132 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4636 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4731 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4885 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 5031 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4450 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4609 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4617 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4785 ms.
//time cost of changing CellBackColor of 50x20 1000 cells = 4886 ms.
//
//time cost of changing CellBackColor of 20x50 1000 cells = 6865 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7759 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7692 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7235 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7821 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7282 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7529 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7357 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7168 ms.
//time cost of changing CellBackColor of 20x50 1000 cells = 7556 ms.

		}
		catch (Exception e)
		{
			e.printStackTrace ();
		}
	}
};

try
{
	threadTest.start ();
}
catch (Exception e)
{
	e.printStackTrace ();
}

return 0;

A screenshot

why ?! :thinking:

scripting on an displayed document is probably the worst approach – after simulating menu actions maybe :wink:

Basic seems to be faster :
100x10 in ~1s on a new sheet
but 100x100 in 40s, then ~10s for 100x10 on this same sheet

  sel = ThisComponent.CurrentController.ActiveSheet
  t0 = Now
  for r = 0  to 50
    for c = 0 to 20
      cell = sel.getCellByPosition(c,r)
 	  cell.CellBackColor = c*r 'Rnd() * &HFFFFFF 
 	next
  next

depending on your use case, generating an ODF would defintely not take more than tenths
Documentation/ODF documents generation tools - The Document Foundation Wiki

My use case would be “implementing an image viewer or video player using cell background”, just for fun.

An image (LibreOffice Calc icon) displayed using cell background

I already did this using Yozo Office (永中Office in Chinese), and the performance is acceptable, I can got 10 fps to play a 52x30 resolution video using Yozo Office.

The speed will slow down if the drawing cells are inside the visible area, so 10x100 would be slower than 100x10 when the window is maximized in a 16:9 screen.

Even basic is faster, it’s still not fast enough to to play a video. 50ms-100ms per frame would be appropriate, which means the time cost of changing CellBackColor of a single cell should be less than 64 μs when 10fps on 1560 cells is expected.

Java is my favorite language, beanshell can execute java code fragment, and macro in Yozo Office is Java too, so I can porting my macro code in Yozo Office to LibreOffice with small modification.

Just a thought: What would be the performance of setting up Conditional Formatting on the viewport cells then simply setting a number value in each cell that triggers the conditional color desired. Of course, set text and background colors the same to make the numbers themselves invisible.

Back in Excel 2009 or so, I recall doing this to create cellular automata displays that were much, much quicker than cell-by-cell formatting. To parallel what I did, one would use the myRange.setData(myArray) API call to set the cell values.

I have attached a quick example, though, that uses RANDBETWEEN. By just holding down the F9 key I get better than 10fps at 52x30 with 3 colors (well, it “feels like” better than 10fps).

First, I would create styles at the color depth I thought I could live with. For example, for 9-bit color one could use RGB values 0 to 7 for each R,G, and B, and so create styles Color000 to Color777 with the appropriate background colors, all inheriting from ColorA to have a universal master style.

To do that and stay sane, I would generate the XML for the styles using my favorite scripting language and add that to the styles.xml file that is in the ODS file zip structure (accessible by changing the name of the ODS file to a .zip extension). Then, similarly, I’d create a script to generate the conditional format definitions and add those to the content.xml file in the ODS zip structure.

The style definitions in the styles.xml XML look like:

<style:style style:name="Color1" style:family="table-cell" style:parent-style-name="ColorA">
<style:table-cell-properties fo:background-color="#f10d0c"/>
<style:text-properties fo:color="#ff0000"/>
</style:style>

[Here I just used Color1; I didn’t try to make the style name be ColorRnGnBn. But in theory at 9-bit color a person would put the value, say, 444 into a cell to get grey by having that value trigger the conditional format to use style Color444 with the fo:background-color=#808080. (Hex math done off-the-cuff).]

The style application in the content.xml XML occurs in two places (I think because of LibreOffice extensions to the ODS format). They look like:

<style:style style:name="ce2" style:family="table-cell" style:parent-style-name="Default">
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" fo:border="none" style:vertical-align="middle"/>
<style:paragraph-properties fo:text-align="center" fo:margin-left="0in"/>
<style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/>
<style:map style:condition="cell-content()=1" style:apply-style-name="Color1" style:base-cell-address="Sheet1.D6"/>
<style:map style:condition="cell-content()=2" style:apply-style-name="Color2" style:base-cell-address="Sheet1.D6"/>
<style:map style:condition="cell-content()=3" style:apply-style-name="Color3" style:base-cell-address="Sheet1.D6"/>
</style:style>

and then

<calcext:conditional-format calcext:target-range-address="Sheet1.D6:Sheet1.AG57">
<calcext:condition calcext:apply-style-name="Color1" calcext:value="=1" calcext:base-cell-address="Sheet1.D6"/>
<calcext:condition calcext:apply-style-name="Color2" calcext:value="=2" calcext:base-cell-address="Sheet1.D6"/>
<calcext:condition calcext:apply-style-name="Color3" calcext:value="=3" calcext:base-cell-address="Sheet1.D6"/>
</calcext:conditional-format>
</calcext:conditional-formats>

I have no idea how many styles one can practically add to an ODS before things start breaking.
RasterColor.ods (28.3 KB)

1 Like

WOW, using Conditional Formatting seems to be a brilliant idea, it will not create new styles when running, I think the performance will be hugely increased, I have to try it.

Seems like converting true color to indexed color in GIMP, color will be lossy, but let me try if it’s acceptable.

This reminds me that when generating excel file using apache poi API, I also composed some extra XML data to get data bar conditional formatting, like this answer on stackoverflow site.

Good to know the formatting of context.xml and styles.xml files, thank you.

Let me started from 2^15 = 32768 styles, which should hold 15bit color style items.
If fails, then fall back to 2^12 = 4096 styles.
If fails again, then fall back to 2^9 = 512 styles, etc…

Let the spreadsheet application do the job.
Horse_Movie.ods (68.0 KB)

4 Likes

@Villeroy: cool …

Horse_Movie_CF.ods (124.0 KB)
includes the “production studio” I used to use in 2012 with OpenOffice. It also embeds the Python code that needed to be downloaded separately in the original topic on forum.openoffice.org.

As far as I can see, I wrote 6 macros:

  1. start the animation (perform integer loop in the cell named “offset”).
  2. stop the animation (call Basic command end)
  3. delete all children of cell styles “Black”
  4. create new children of cell style “Black” from a selection of 2 columns for style name and decimal color value
  5. “read_ConditionalFormats” (Python, blue button) reads conditional formats from named range “readCF” and applies them to the currently selected range. Select named range “Screen” before calling the macro.
  6. “write_ConditionalFormats” (Python, blue button) dumps the active cell’s conditional formatting to a named cell “writeCF”. Clear the range around that cell, select any cell in range “Screen” before calling the macro.

Cell range “readCF” is a scenario range which allows switching between different sets of conditional formatting rules.

How to switch the celluloid:

  1. Choose another scenario.
  2. Clear the old cell styles (macro no. 3)
  3. Select the array at BB33 right of the scenario range and call macro no. 4 to create new styles based on the new values.
  4. Select the range named “Screen” and call macro no. 5 which applies the c.f. rules.
3 Likes

Here is a function that will generate a reduced color scheme. It can be used to generate a palette directly.
~
So, for example, instead of a() = rg.getDataArray() one could use a() = GenerateColors() in @Villeroy’s code Main in Module2.
~

Function GenerateColors()
	REM Generate a reduced color palette from 256 gradations per R,G,B to n gradations per R,G,B
	REM    Reduced colors are centered in their range
	REM       For example, with n=4 the R,G,B values would be hex 20,60,A0,E0
	Dim StyleMap() As Variant
	Dim Depth As Long
	Dim HalfFactor256 As Long
	Dim StyleRGB As Variant
	Dim StyleR As Long
	Dim StyleG As Long
	Dim StyleB As Long
	Dim Index As Long
	
	REM Changing this is, honestly, not yet tested
	Const GradationsPerRGB = 4
	REM Get offset to center R,G,B values in their range
	HalfFactor256 = 128 \ GradationsPerRGB
	REM Determine needed number of colors
	Depth = GradationsPerRGB^3
	REM Set up outer array. Since used with UNO API, 2D array will be nested, not a BASIC 2D array
	ReDim StyleMap(0 To Depth - 1) As Variant
	
	REM Build the array to use for setting up Calc styles
	For Index = LBound(StyleMap) To UBound(StyleMap)
		StyleB = (256 \ GradationsPerRGB) * (Index Mod GradationsPerRGB) + HalfFactor256
		StyleG = (256 \ GradationsPerRGB) * ((Index \ GradationsPerRGB) Mod GradationsPerRGB) + HalfFactor256
		StyleR = (256 \ GradationsPerRGB) * ((Index \ GradationsPerRGB^2) Mod GradationsPerRGB) + HalfFactor256
		StyleRGB = RGB(StyleR,StyleG,StyleB)
		REM Give styles a lengthy name for testing and integration. Probably change for actual use...
		StyleName = "Clr_" & CStr(Index) & "_" & Hex(StyleR) & "_" & Hex(StyleG) & "_" & Hex(StyleB)
		REM Set up the inner array, the nested portion of an UNO 2D array
		StyleMap(Index) = Array(StyleName, StyleRGB)
	Next Index
	
	GenerateColors = StyleMap
End Function

@Villeroy
Loos incredible!
And the fps is acceptable on my poor machine.

@joshua4
Thanks for your kindly help.

Yesterday, I wrote similar code in bash (generate palette) & java (get palette index from RGB value), tested dozens times, and found several ways to increase the performance:

  • Use CellRange.setDataArray to set bulk data, as you said in your first reply.
  • Set the font size to minimal (6pt), and change the column width wider enough to display cell value without a red “:arrow_backward:” sign occured in the cell.
    The red “:arrow_backward:” sign will slow down the performance badly.
  • Decrease the conditional formatting rules:
    less rules means faster speed, but it also means the lossy of colors will be increased.
    On my poor computer, the performance using 64 rules (6bit color/style palette) will be acceptable, it can reach around 3fps to 6fps. 32768 rules (15bit color/style palette) will be about 10+ times slower.

bash script to generate some contents in styles.xml

for ((r=0; r<=$rgb_single_color_mask; r++))
do
	for ((g=0; g<=$rgb_single_color_mask; g++))
	do
		for ((b=0; b<=$rgb_single_color_mask; b++))
		do
			r_8bit=$(( 255 * $r / $rgb_single_color_mask))
			g_8bit=$(( 255 * $g / $rgb_single_color_mask))
			b_8bit=$(( 255 * $b / $rgb_single_color_mask))
			color_hex=$( printf "%02x%02x%02x" $r_8bit $g_8bit $b_8bit )
			# cfsi: Conditional Formatting Style Index
			echo '<style:style style:name="cfsi'${color_hex}'" style:family="table-cell" style:parent-style-name="cfsiDefault"><style:table-cell-properties fo:background-color="#'${color_hex}'"/><style:text-properties fo:color="#'${color_hex}'"/></style:style>'
		done
	done
done

java/beanshell code to map RGB value to style palette index

for (int iRow=0; iRow<nHeight; iRow++)
{
	for (int iCol=0; iCol<nWidth; iCol++)
	{
		int nRGB = img.getRGB (iCol, iRow) & 0xFFFFFF;
		int r = (nRGB >> 16) & 0xFF;
		int g = (nRGB >>  8) & 0xFF;
		int b = (nRGB      ) & 0xFF;
		int r_n_bit = Math.round (nRGBSingleColorMask * r * 1.0 / 255);
		int g_n_bit = Math.round (nRGBSingleColorMask * g * 1.0 / 255);
		int b_n_bit = Math.round (nRGBSingleColorMask * b * 1.0 / 255);
		int nRGB_n_bit__as_index = (r_n_bit << (2*nRGBSingleColorBitDepth)) | (g_n_bit << nRGBSingleColorBitDepth) | (b_n_bit);
		arrayCellRangeValues[iRow][iCol] = Integer.valueOf (nRGB_n_bit__as_index);
	}
}

1 Like

If you get to a settled version, would you be willing to share an upload?

@joshua4

Currently, the macro code are written in beanshell.

The video player is actually an image series player, you have to specify a directory path in the code.

And the image size for the conditional formatting versions should be 52x30, it could be any image type that supported by java.

If you want a delay between frames, uncomment the //java.util.concurrent.TimeUnit.MILLISECONDS.sleep (NNN) line, and change the delay time NNN.

image-series-player_with-conditional-formatting_6bit-64colors.ods (18.2 KB)
image-series-player_with-conditional-formatting_9bit-512colors.ods (28.3 KB)

(Sorry for my sh**ty coding style.)

1 Like

Another two versions.
image-series-player_with-conditional-formatting_12bit-4096colors.ods (100.5 KB)
image-series-player_with-conditional-formatting_15bit-32768colors_loading-speed-will-be-slow.ods (687.6 KB)

And the original version - change the cell background color directly.
image-series-player_with-changing-cell-background-color.ods (11.0 KB)

Also, the bash scripts to generate part of the XML contents for styles.xml and content.xml.
bash scripts to generate the part of XML contents for styles.xml and content.xml.zip (2.3 KB)