Showing posts with label GIMP. Show all posts
Showing posts with label GIMP. Show all posts

Thursday, November 5, 2015

More thoughts on the 'color' blend mode

In the previous post, I discussed the 'color' image blend mode as used by GIMP and my efforts in Matlab to make a more useful variation.  To recap, methods such as HSV, HSI, and HSL in general offer poor separation of color and brightness information.  Of these methods, HSL tends to be the most useful for color adjustment and blending due to the symmetry of its saturation space, despite the fact that L is a poorer representation of image brightness than I.  Unconstrained operation in other color spaces (LAB, LUV, YPbPr) leaves us with the problem of how to deal with out-of-gamut conditions on conversion back to RGB.  Calculating boundary chroma and performing data truncation prior to RGB conversion can solve the problems caused by out-of-gamut points, resulting in good retention of background brightness.

When I had originally approached the use of LCH for image blending operations, I began by using a constrained and normalized version of LCH known originally as HuSL (now called HSLuv).  For a speed increase I also used a normalized polar version of YPbPr I refer to as HSY.  In these variations, saturation is normalized to the chroma range of the projected RGB space.  This is convenient and solves most of the same issues which a constrained LCH method solves.  It offers a few conveniences and features, but it has some weaknesses as well.  The normalization of C causes colors of constant S to no longer have a uniformity of appearance, but repeated hue adjustments do not cause chroma compression as with LCH truncation.

The projection of RGB in CIELAB and YPbPr as the extent of HuSLab and HSY
One of the drawbacks of normalizing or bounding chroma to the extent of the projected RGB space in LCH is that only a fraction of the projected RGB space lies on continuous locus of constant chroma. There exists only a small subset of the RGB space wherein color points can be subject to arbitrary hue rotation without leaving the gamut and having their chroma changed by truncation.  This maximal rotationally-symmetric boundary defines the normalizing chroma used for HuSLp, a variation of HuSL wherein the uniformity of the parent space (LAB or LUV) is retained.  As HuSLp and HSYp are subsets of the projected RGB space, they obviously cannot specify the full range of RGB colors.  Specifically, they are restricted to colors near the neutral axis, hence the suffix denoting pastel.  Compared to HuSLp, HSYp is not as uniform, but it has access to a greater fraction of the RGB space than HuSLp in either CIELAB or CIELUV modes.

Maximal biconic subsets of RGB in CIELUV, CIELAB, YPbPr
Uniformity in HuSLp (LAB): surfaces of 100% S and 50% L
Uniformity in HSYp: surfaces of 100% S and 50% Y
While this makes HuSLp and HSYp useful for color selection tasks, they are also useful for image editing so long as the chroma limiting is acceptable. Consider a color blending task with highly saturated foreground.  With its larger color range, let us compare a color blend in HSYp and LCHab.

Extreme test foreground and background
Color blends in CIELCHab and HSYp

While the limitations of HSYp are noticeable, the benefits are far more striking in this case.  The blue, red, and green stripes are moderated and the image regains its depth. 

To be realistic, this use-case is impractical but for testing.  A foreground with moderated colors would be less influenced by the limited range of HSYp.  For a sufficiently desaturated foreground, HSYp blending would produce results similar to a LCH blend, but it may still be quite a bit faster.  The major convenience of HSYp color blending is simply that the chroma is always compressed to the maximal uniform boundary.  There is no need to tweak foreground saturation to prevent uneven results.

Obviously, using stripes makes it difficult to produce a pleasing blend in any case, but that's kind of the point when testing.  In general, image editing operations need to consider content as much as method.  Blending a broad range of saturated hues with a photographic scene tends to create results which defy a viewer's expectations of reality.  Seeing a person with a purple face or a wood table lit with rainbow light doesn't make any sense to a pattern-recognition machine built on earth.

The image manipulation toolbox I've posted contains HSYp and HuSLp conversion tools, as well as support for these methods in the related image blending function (imblend) and the color adjustment function (imtweak). 

Sunday, November 1, 2015

Exploring the 'color' blend mode

In my ongoing adventures in creating image manipulation tools for Matlab, I have spent quite a bit of time getting desirable behavior from blending and adjustment tools.  Although most of these problems share a common set of core difficulties, perhaps one of the more intuitive ways to explore the issue is through the common "color" layer blending mode.


In image editing tools such as GIMP and Photoshop, image layers can be combined or blended using different pixel blending modes.  While modes such as the contrast enhancing modes don't suggest their operating methods so directly, certainly the component and arithmetic modes do.  Addition and division modes suggest the underlying math.  Hue and saturation suggest the channels to be transferred between foreground and background.  The detail-oriented observer might begin to raise questions at this point.  Hue? Saturation?  In what model are we working?

What about the 'color' mode?  Mere observation suggests that it's a combination of hue and saturation information being transferred, but again, the details are critically important. Various sources online suggest that this mode performs a HS transfer in HSV... or HSB... or HSL... or HSY?  One begins to wonder if anybody knows what they're talking about.

As my experience lies with GIMP, I based my expectations on its behavior.  Certainly, I can't test my blending algo against software I don't have; however, it might be worth keeping in mind that other suites may use different approaches.  The passing impression of online resources suggests that GIMP uses HSV or HSL, Photoshop uses CIELAB, and according to simplefilter.de, PaintShopPro uses some bastard HSL/HSY swap that has people so baffled that they ask questions of forums only to get more nonsensical guidance.   Let's sort this out as best we can by simple experiment.

I assume at this point a few things about what is desired.  Forgive my loose language hereon, but in this pixel blending mode, what should be transferred is the chroma information independent of the luminosity-related information.  In other words, the hue and colorfulness should change, but not the perceived brightness.  White should remain white, black should remain black.  Acronyms make it sound like a simple task, don't they?

Let's start with HSV, HSL, and HSI.  These are polar models used to express the content of the sRGB color space.  The convenience of these familiar models is simply the manner in which their polar format allows intuitive selection and adjustment of colors.

HSV is commonly visualized as a hexagonal cone or prism defined by piecewise math.  HSI is expressed as a cylinder or cone defined by simple trigonometry.  HSL is often described as a hexagonal bicone.  What's with all the different geometry?  All we want to do is separate HS from L/V/I, right?  Feel free to look at the math, but I think it's more valuable to the novice (me) to see how the isosurfaces associated with these models are projected into the sRGB space.

Isosurfaces of Value and Intensity as projected into the RGB cube

Value, the V in HSV, is defined as the the maximum of the RGB channels.  This means that the isosurfaces of V are cube shells in RGB.  Intensity (I in HSI) is the simple average of the channels.  This translates to planar isosurfaces.  At this point, it might start to look like HSI would provide more meaningful separation of color information from brightness information.  Does it make sense that [1 1 1] and [1 0 0] have the same brightness metric as they do in HSV?  Let's try to do an HS swap in these models and see what happens.

Foreground image

Background Image
Color blend in HSV

Color blend in HSI
Well, those look terrible.  White background content is not retained, and the image brightness is severely altered.  What happens with HSL?

Color blend in HSL
Well that looks better.  In fact, that looks exactly like GIMP behaves.  Checking the source code for GIMP 2.8.14, this is exactly how GIMP behaves. Let's look for a moment at why the white content is preserved.  While I previously compared the projected brightness isosurfaces to mislead the reader as I had misled myself, the answer lies in the projection of the saturation isosurfaces.  Simply put, it is the conditional nature of the calculation of S in HSL which creates its bicone geometry and allows comparable S resolution near both the black and white corners of the RGB cube.

Saturation isosurfaces as projected into the RGB cube from HSV, HSI, HSL
So we know how GIMP does it.  What are better ways?  While HSL helps prevent the loss of white image regions, the overall brightness still suffers.  Let's start with the vague suggestion that Photoshop uses CIELAB.  At this point, I can only make educated guesses at the internals, but intuition would suggest that we might try using CIELCHab, the polar expression of CIELAB.  In naivety, I perform a CH swap to obtain the following.

Color blend in CIELCHab
Now things are actually worse.  Both black and white regions are lost.  To understand why, I put together a tool for visualizing the projection of sRGB into other color spaces such as CIELAB.

The trajectory of an out-of-gamut point in unconstrained LCHab and HuSLab
Here, we see the volume containing all RGB triplets whose values lie within standard data ranges (e.g. 0-255 for uint8).  Unlike the case with HSV, HSL, and HSI, the sRGB space is a subset of the CIELAB space.  A color point which lies outside the depicted volume would have pixel values that are either negative or larger than allowed by the datatype.  Upon conversion back to RGB, these values would be truncated to the standard range. In this manner, color points outside the cube will be mapped to points on its surface. Since the truncation occurs after conversion to RGB, the trajectory of out-of-gamut color points will be parallel to the axes of the cube.  It then stands to reason that the orientation of the neutral axis means that out-of-gamut points tend to be mapped away from the white and black corners of the cube and toward the primary-secondary edges.

If the goal remains to isolate the effects of color manipulation and image brightness, the geometry of this projection poses an issue.  To transfer the radial position of a primary or secondary corner to a pixel which is near the extent of the L axis would be to push the pixel outside the cube.  If we were to rotate the hue of a pixel which lies on a primary or secondary corner, it would again move outside the cube.  How can we manage the existence and graceful conversion of these points?   To be honest, I'm not sure I know the best methods.  All I can offer are my current approaches.

One approach would be to do data truncation prior to RGB conversion.  Of course, the maximum chroma for a given L and H is not simple to calculate, but this would keep the trajectories of out-of-gamut points in a plane of constant L.  This produces good results, but reveals an issue with working methods.

Color blend in CIELCHab
Ideally, we would try to do all our image operations in the uniform space (LAB/LUV) and only perform the conversion to RGB for final output or screen display.  At the very least, we could convert to RGB with no data truncation, allowing for negative and supramaximal pixel values to retain the information stored by OOG points.  For my efforts in creating standalone tools with RGB input and output, this best approach seems terribly impractical.  When constrained by compatibility with Matlab's image viewer and other image handling functions, we're forced to decide whether it's appropriate to lose chroma information by truncating in LCH.  Even though it causes no ill effects for a single operation, it means that successive operations will tend to compress the chroma content of the image to a small subset of the projected RGB space.

This is more of a problem for image and color adjustment than for a single channel swapping blend operation, but it's something to keep in mind.  I'll probably come back to the topic of normalized versions of LCH at a later date.

If implementing a constrained color model like this is beyond the realm of practical effort-costs, consider this simple dirty method to retain brightness in a color blend.  Simply copy the background luma, perform a HS channel swap in HSL, then re-establish the original luma using your favorite luma-chroma transformation.  This might seem like it would waste a lot of time with converting twice, but it's often the fastest method among those I've described.  The results are similar to the normalized methods, and existing conversion tools can typically be used.

Color blend in HSL with Y preservation
For an example of the methods, feel free to check out the image blending function I've posted on the Mathworks file exchange. The conversion tools for LCHab, LCHuv, HuSLab, HuSLuv, and HSY are included in the parent submission.

Wednesday, July 1, 2015

Revisiting STARS-922 and Other Stuff

There seems to be some skepticism and curiosity that surround STARS-922 thermal compound (aka "heat sink plaster") and some similar products.  I know I've wondered for a while myself and I've run across a handful of forum posts that question its purpose or properties.  Places like DealExtreme and Ebay sell lots of this product along side other more common zinc-oxide thermal pastes with limited product differentiation.  The poorly translated and dubious specifications and naming don't help to instill confidence that it is trustworthy of any particular application. On some occasions, customer reviews boast "strong" or "excellent conductivity" although i doubt either metric had been actually evaluated beyond the emotional satisfaction associated with the completion or failure of the parent project as determined by myriad other factors. I've found few satisfactorily detailed reviews, so I figured I'd add this bit of info to augment my halfass thermal testing from the prior post.

What the hell do you mean "plaster"?

Some of the questionable property specifications are:
  • Thermal conductivity: > 1.2W/m-K
  • Thermal Impedance: < 0.06 (what units?) 
  • Clotting time: 3min (25 degree celsius) (all of it or just the exposed stuff?)
  • Strength of connected buildings: 25Kg  (what?) 
  • Temperature resistance: 200 degree celsius
Following some of my own experience and a few comments noting that it had little adhesive capability, I figured I would try to get a better feel for the mechanical properties of the product in a way that should at least lend intuition a better handle on what's appropriate to expect.

First off, this product is not a thermal grease.  It is much more viscous and does indeed congeal on exposure to air. The cured product is slightly rubbery, but not as elastic as one might expect from a conventional RTV silicone caulking.  The most common place I have seen this is in the bonding of the MCPCB to the radiator body of cheap LED retrofit lamps.  In such an application, temperatures are low and mechanical stress is small.  The MCPCB has only a small mass, and the aspect ratio ensures that it is difficult to produce a tension stress at the interface.  This is all overshadowed by the advantage of being enclosed.  Nothing can stress the bonded part except its own inertia.  This seems to be a rather forgiving application, mechanically speaking.  Will it work for larger SMT heat sinks or other things?  We need to have a bit more understanding about its actual strength in shear and tension.


In order to test shear and tension strength, I put together a setup in the lathe consisting of various repurposed fixturing and a 1000# load cell with a selectable-gain instrumentation amplifier.  Since I never finished the microcontroller DRO that was supposed to go with the amplifier I made for this load cell, I have to use the multimeter to take direct readings.  Despite the extra clumsiness of operations, the results would be the same.  To get the approximate peak value at break, I simply used the phone to record operations.  Accepting that the periodic multimeter update is the result of an averaging operation and may lag the actual value, and forgiving my inability to produce a smooth and monotonically increasing tension by handscrew, the captured values should be acceptable for the intended purpose if averaged across a few samples.

some assembled samples and spring clamp for alignment

The shear samples consist of aluminum plates cut so that they produce a lap joint with approximately one square inch of area.  The tension samples are old elevator bucket bolts that have had their head faces ground and scoured.  All samples are ground and scoured in degreaser as needed to prepare a fresh surface each time.  No particular assembly fixturing was used; no clamping was performed except to gently maintain alignment with adhesives that were flowable (e.g. molten hot glue).  Samples were tested with heat sink plaster, some Permatex RTV silicone products, as well as generic superglue and two unknown hot glue products.  One was a soft and flexible hobby-marketed product, and the other was a harder Arrow brand carpentry-marketed product, if I recall correctly.



In approximate terms, the heat sink plaster (HSP) is about on par with a low-strength RTV silicone.  As with the prior thermal resistance testing, I ran a few sample groups of Permatex Ultra Grey RTV and High Temp Red RTV gasketing products.  These represent the extremes of the product spectrum in terms of specified tensile strength and filler density.  It's a fair bet to assume that if you'd be comfortable with the strength of your favorite RTV silicone, the strength of HSP will be sufficient for your task.  There are a few things to consider, though.

One of the primary obstacles I ran into with this setup is that I'm testing air-cured materials in thin sections.  With nonporous sample substrate, the geometry of the test patch does end up becoming important.  Especially with attempts to cure products at room temperature, it was not uncommon to find samples that were only cured on an annulus around the perimeter of the bond patch.  Most of these partially-cured sample groups were discarded for various reasons, typically nonuniformity or large variance within a group.  In the case of some samples with uniform partial curing, I made my best attempt to measure the effective cured area using image processing after the break test.  While I note that my goal here is to measure strength of the cured adhesive fraction, I did not make any differentiation in the thermal testing process.  It may be worth knowing if the thermal resistance is much higher when the center of the sample patch has cured completely, but I can't exactly put the thermal test fixture in the oven with all its plastic bits.  I'm also far too lazy to do larger test groups over a longer natural curing interval of say a month. 


For what it's worth, the image processing used to calculate partial bond area is rather simple and goes to show that GIMP or Photoshop are useful for more than just meandering artfaggotry drawing and editing photos.  It goes like this:
  • Remove uncured adhesive with solvent
  • Take photos of both halves
  • Transform image areas to geometric correspondence
  • Perform threshold operation to isolate cured material
  • Multiply images to get combined coverage
  • Find average pixel value
The example shown is not a particularly well-cured sample (42% cured), but it was from a group that were uniform.  

In my attempts to expedite thorough curing of the samples, I tried several runs at elevated temperatures in the oven.  Some of these resulted in reduced and typically inconsistent strength.  In the case of the RTV, some of the extended cure times (96 hours at 70-80 °C) showed very low shear strength, and the bond patterns were not characteristic of radial curing.  My guess is that as the silicone cured, it becomes increasingly difficult to outgas any remaining volatile products and pressure produced tear channels in the material near which further curing can take place.

some bad rtv shear samples

Some of the samples of heat sink plaster which were cured at high temperatures (100 °C) became dry and friable, taking on a tan color.  These samples still exhibited normal shear strength, but had dramatically reduced tensile strength.  Whether this loss of pliability and tensile strength occurs at lower temperatures over a longer time frame is unknown, but could potentially be a limiting factor in reliability.  It also calls into direct question the product specifications.  I doubt that the dry state is an intended condition for the product; consequently, I doubt that the 200 °C rating is realistic.  The condition of samples cured at 70 °C was significantly better than those cured at only 30 °C more.

Overall, the product performance is more than adequate for small heat sinks, particularly low-height heat sinks.  The thermal performance is not exceptional, but for the same area, it has a similar thermal resistance compared to a grease, despite forming a relatively thick interface film.  I still would hesitate to refer to it as a self-shimming type TIM, as I doubt that it's intended to serve as an electrical insulator.  That said, It's important to note the limitations of these tests.

All of the RTV silicone and HSP samples are very fresh and really don't represent thoroughly cured material.  It may be reasonable to expect stronger bonds over a time span of a month or so, depending on the ability of the center of the bond patch to breathe.  Similarly, these tests do not indicate whether strength or thermal performance will be sustained over much longer time frames at elevated temperatures.  Longevity is still uncertain, and performance at temperatures beyond 100 °C is questionable. 

That said, there are always other products available that fill a similar role.  STARS-922 is simply the cheapest option.  There are other cheap options out there in the realm of silicone products, and there are also epoxies for use when greater strength is desired and disassembly isn't intended.  There are always the myriad products from Loctite and Dow for specific applications, though most of these are far too expensive for the hobbyist.

One other note as an aside: the hot glue samples were not applied with a glue gun.  These, like most cases where I use hot glue, were applied with a heat gun.  This is due to the simple fact that molten glue typically lacks the thermal capacity to heat a conductive substrate sufficiently to allow bonding to take place before the glue solidifies.  Disregard for this simple fact appears widespread, as it seems every time I encounter hot glue in assembled products, it simply pops off the surfaces cleanly.  So long as you can heat parts manually, hot glue does indeed have utility outside the realm of gluing googly eyes onto cotton balls.  Even the cheap hobby products are incredibly strong so long as the application temperatures stay low.

Finally, I'll leave you with this bit of Matlab kludge that I put together to create the bar graph above.  I always get so frustrated trying to use spreadsheet graphing utilities to do anyhing.  It's impossible to get half the features that are desired, and the figures come out with cartoonish inelegance and poor repeatability.  Granted, a configuration with proportionally-correspondent dual axes is a hassle even in Matlab, but at least Matlab provides a superabundance of flexibility.  One might ask why anyone would ever need dual x or y axes, but the simple answer seems like a terribly common necessity to me: depicting one set of data in both metric and imperial units. 

%% plot adhesive properties
clc; clf; clear all
format compact;

xt={'HT RTV 48h @ 70dC' 'HSP 24h @ 25dC' 'HSP 48h @ 70dC' 'UG RTV 48h @ 70dC' ...
    'Super Glue (generic)' 'Hot Glue (soft clear)' 'Hot Glue (hard amber)'};
ytkpa=[1299 1366 1860 2418 2552 3354 4758];
scalefactor=690/4758;
ytpsi=ytkpa*scalefactor;
scalelabels={'kPa' 'PSI'};
titlestring='Tensile Stress at Break';
ystretch=0; % stretches bar widths for better fit

% create first axis
hl1 = barh(1:1:length(xt),ytkpa);
ax1 = gca;
set(ax1,'XColor','r','YColor','k','box','off','color','none','YTickLabel',xt,...
    'XAxisLocation','bottom','YAxisLocation','left','XGrid','on',...
    'TickLength',[0.015 0],'ylim',[0+ystretch length(xt)+1-ystretch])

% this hackery removes the top and right tick marks from ax1
b = axes('Position',get(ax1,'Position'),'box','on','xtick',[],'ytick',[]);
axes(ax1); linkaxes([ax1 b]);

% create second axis
ax2 = axes('Position',get(ax1,'Position'),'XColor','b','YColor','k',...
    'XAxisLocation','top','YAxisLocation','right','Color','none',...
    'XGrid','on','Ytick',[],'TickLength',[0.02 0],...
    'xlim',get(ax1,'xlim')*scalefactor,'ylim',get(ax1,'ylim'));
hold(ax2,'on')
hl2 = barh(1:1:length(xt),ytpsi,'Parent',ax2);
set(hl2,'facecolor',[1 1 1]*0.5,'edgecolor',[1 1 1]*0);
set(get(hl2,'BaseLine'),'color','k')

% set and position the labels and titles
xl1=xlabel(ax1,scalelabels(1),'units','normalized','position',[-0.1 -0.02 0]);
xl2=xlabel(ax2,scalelabels(2),'units','normalized','position',[-0.1 1 0]);
t1=title(ax2,titlestring,'units','normalized','position',[0.5 1.09 0],'fontweight','bold');

% apply value labels on bars (not exactly robust geometry retention)
for n=1:1:length(xt)
    scalemax=max(get(ax2,'xlim'));    
    valb=text(ytpsi(n)-0.01*scalemax,n-0.0,['\color {red}' num2str(round(ytkpa(n))) ...
        ' \color {blue}' num2str(round(ytpsi(n)))], ...
        'horizontalalignment','right','fontweight','bold');
end

% this mess massages the plot to fit well within its container
marginoffset=[0.04 0.01];
margins=get(ax2,'tightinset'); h=[margins(1) margins(3)]; v=[margins(2) margins(4)];
set(ax1,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])
set(ax2,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])
set(b,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])

% updating axis sync without this is a bunch of shit
% similar to 'linkaxes()', but works with unequal axis limits (and 3 axes)
addlistener(ax1,'Position','PostSet',@(src,evt) updateAxis(ax1,ax2,b));
function updateAxes(a1,a2,a3)
    % callback function for updating internal plot geometry
    % to keep axis objects aligned if container geometry is altered
    xLim1=get(a2,'xLim');
    yLim1=get(a2,'yLim');

    set(a2,'position',get(a1,'position'));
    set(a3,'position',get(a1,'position'));
    set(a2,'xLim',xLim1);
    set(a2,'yLim',yLim1);
end

Wednesday, September 10, 2014

Automatic GIMP dock window exporting


To my knowledge, GIMP is unlike other applications under X in one particular sense; it has the inbuilt functionality to export its windows to other X displays. Besides the bizarre, a more common application is to move GIMP windows onto different monitors (screens) in a multihead configuration (Zaphod mode, not Xinerama). This functionality is available on the window menus and in theory, these window locations should even be saved in the sessionrc data. ... But that's where everything breaks.
The export menu items

In my experience with GIMP 2.8.2 and a few earlier versions under xfwm4/Mint14, the sessionrc file normally contains all the correct geometries for a dock window which has been exported to another X display; however, when GIMP exports the window during launch, the window is initialized at a minimal size instead of the specified overall geometry. Resizing the window like this destroys any specific internal geometries (i.e. the relative widths of individual dock tabs). I'm not terribly sure, but i suspect that it's a window manager problem and that a simple delay during the export process would fix the problem.

The collapsed dock window.  Internal geometry is lost.

My workaround was to simply automate a manual window export routine. Since the two displays normally used for both the GIMP image window and the exported dock have the same dimensions, I simply configure both to be created on the same display. I then set GIMP preferences to save the window positions. Subsequently, i turn off session save features and even set sessionrc to read-only (just for good measure).

A short script using xdotool does all the work:

#!/bin/bash
# this script launches gimp and exports the tools/layers dock window
# this avoids the fact that gimp's session manager cannot accurately place the
# window if exported during launch
# may be due to laggy wm data during concurrent placement of other gimp windows
# click placement is dependent on screen dims, themes

gimp &

while [ "$(wmctrl -l | grep "Layers")" == "" ]; do
  sleep 0.1
done

DISPLAY=:0.2
wmctrl -s 1
sleep 0.5
DISPLAY=:0

keytime=0.005

xdotool search --screen 0 --name "Layers" windowfocus;
sleep 0.1; xdotool mousemove 1266 60
sleep 0.3; xdotool click 1

for i in `seq 1 12`; do
  sleep $keytime; xdotool key Down
done

sleep $keytime; xdotool key Right

for i in `seq 1 3`; do
  sleep $keytime; xdotool key Down
done

sleep $keytime; xdotool key Return

echo "window move attempted"

It's a cringe-worthy open-loop sort of approach, but it does work. This script launches GIMP with the unexported dock window on the same screen. It raises the dock window, clicks on the menu button, and uses key events to navigate to the desired menu entry.

Simply tailor the script to fit your application:
  • menu button location (1266 60)
  • $DISPLAY values for particular screens
  • dock window title string
  • number of key events to reach your menu item
  • adjust key delay if necessary