function animatedGears() % Source code for drawing gears % The shape of the gears is not precise, it creates a decent GIF and a SVG. % % 2019-05-12 Jahobr [pathstr,fname] = fileparts(which(mfilename)); % save files under the same name and at file location RGB.bkgd = [1 1 1 ]; % white background RGB.black = [0 0 0 ]; % RGB.green = [0.1 0.7 0.1]; % RGB.yellow = [1 0.7 0 ]; % RGB.blue = [0.2 0.2 1 ]; % RGB.red = [1 0.2 0.2]; % % violetRGB = [0.6 0.2 0.8]; % RGB = structfun(@(q)round(q*255)/255, RGB, 'UniformOutput',false); % round to values that are nicely uint8 compatible figHandle = figure(15674459); clf set(figHandle,'Units','pixel'); set(figHandle,'MenuBar','none', 'ToolBar','none'); % free real estate for a maximally large image set(figHandle,'Color',RGB.bkgd); % white background axesHandle = axes; hold(axesHandle,'on') axis off % invisible axes (no ticks) axis equal; for currentCase = 3:8 switch currentCase case 1 % animatedGears saveName = 'animatedGears'; nFrames = 240; teeth = [14, 3*14, 14, 2*14]; module = [ 2, 2, 3, 3]; % gear size diameter = module.*teeth; center1 = [0 0]; center2 = [(diameter(1)+diameter(2))/2 0]; center3 = [center2(1)+(diameter(3)+diameter(4))/2 0]; xLimits = [center1(1)-diameter(1)/2-2*module(1) center3(1)+diameter(4)/2+module(4)+module(1)]; % use a rim of size "module(1)" yLimits = [center3(2)-diameter(4)/2-module(4)-module(1) center3(2)+diameter(4)/2+module(4)+module(1)]; % use a rim of size "module(1)" maxMovementOfTheSlowestTooth = 2*pi/teeth(4); anglesSlow = linspace(0,maxMovementOfTheSlowestTooth,nFrames+1); anglesSlow = anglesSlow(1:end-1); % remove last frame, it would be double secondRatio = teeth(4)/teeth(3); anglesMedium = anglesSlow.*secondRatio; anglesMedium = anglesMedium + (2*pi/teeth(3)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT firstRatio = teeth(2)/teeth(1); anglesFast = anglesMedium.*firstRatio; anglesFast = anglesFast + (2*pi/teeth(1)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT case 2 % animated_3_Gear_Row saveName = 'animated_3_Gear_Row'; nFrames = 80; teeth = [14, 2*14, 3*14]; module = [1, 1, 1]; % gear size diameter = module.*teeth; center1 = [0 0]; center2 = [(diameter(1)+diameter(2))/2 0]; center3 = [center2(1)+(diameter(2)+diameter(3))/2 0]; xLimits = [center1(1)-diameter(1)/2-module(1)*2 center3(1)+diameter(3)/2+module(3)*2]; % use a rim of size "module(1)" yLimits = [center3(2)-diameter(3)/2-module(3)*2 center3(2)+diameter(3)/2+module(3)*2]; % use a rim of size "module(1)" maxMovementOfTheSlowestTooth = 2*pi/teeth(3); anglesSlow = linspace(0,maxMovementOfTheSlowestTooth,nFrames+1); anglesSlow = anglesSlow(1:end-1); % remove last frame, it would be double secondRatio = teeth(3)/teeth(2); anglesMedium = anglesSlow.*secondRatio; anglesMedium = anglesMedium + (2*pi/teeth(2)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT firstRatio = teeth(2)/teeth(1); anglesFast = anglesMedium.*firstRatio; anglesFast = anglesFast + (2*pi/teeth(1)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT case 3 % animated_internal_gear saveName = 'animated_internal_gear'; nFrames = 80; teeth = [14, 3*14]; module = [1, 1]; % gear size diameter = module.*teeth; center1 = [0 (diameter(1)-diameter(2))/2]; center2 = [0 0]; xLimits = [center2(1)-diameter(2)/1.5 center2(1)+diameter(2)/1.5]; % yLimits = [center2(2)-diameter(2)/1.5 center2(2)+diameter(2)/1.5]; % maxMovementOfTheSlowestTooth = 2*pi/teeth(2); anglesSlow = linspace(0,maxMovementOfTheSlowestTooth,nFrames+1); anglesSlow = anglesSlow(1:end-1); % remove last frame, it would be double ratio = teeth(2)/teeth(1); anglesFast = anglesSlow.*ratio; anglesFast = anglesFast + (2*pi/teeth(1)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT case 4 % animated_rack_and_pinion saveName = 'animated_rack_and_pinion'; nFrames = 80; teeth = [14, 2*14]; module = [1, 1]; % gear size diameter = module.*teeth; center1 = [0 0]; center2 = [0 -diameter(1)/2]; xLimits = [center1(1)-diameter(1)*1.25 center1(1)+diameter(1)*1.25]; % yLimits = [center1(2)-diameter(1) center1(2)+diameter(1)/2+module(1)*3]; % maxMovementOfTheSlowestTooth = 2*pi/teeth(1); anglesSlow = linspace(0,maxMovementOfTheSlowestTooth,nFrames+1); anglesSlow = anglesSlow(1:end-1); % remove last frame, it would be double sideShift = anglesSlow.*diameter(1)/2; sideShift = sideShift + module(1)*3/8; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT case 5 % animated_two_spur_gears saveName = 'animated_two_spur_gears_1_1'; nFrames = 80; teeth = [14, 14]; case 6 % animated_two_spur_gears saveName = 'animated_two_spur_gears_1_2'; nFrames = 80; teeth = [14, 2*14]; case 7 % animated_two_spur_gears saveName = 'animated_two_spur_gears_1_3'; nFrames = 80; teeth = [14, 3*14]; case 8 % animated_two_spur_gears saveName = 'animated_two_spur_gears_1_5'; nFrames = 80; teeth = [14, 5*14]; end if contains(saveName,'animated_two_spur_gears') % all versions module = [1, 1]; % gear size diameter = module.*teeth; center1 = [0 0]; center2 = [(diameter(1)+diameter(2))/2 0]; xLimits = [center1(1)-diameter(1)/2-module(1)*2 center2(1)+diameter(2)/2+module(2)*2]; % use a rim of size "module(1)" yLimits = [center2(2)-diameter(2)/2-module(2)*2 center2(2)+diameter(2)/2+module(2)*2]; % use a rim of size "module(1)" maxMovementOfTheSlowestTooth = 2*pi/teeth(2); anglesSlow = linspace(0,maxMovementOfTheSlowestTooth,nFrames+1); anglesSlow = anglesSlow(1:end-1); % remove last frame, it would be double ratio = teeth(2)/teeth(1); anglesFast = anglesSlow.*ratio; anglesFast = anglesFast + (2*pi/teeth(1)) *0.5; % ALLIGNMENT; THIS MAY NEED MANUAL ADJUSTMENT end xRange = xLimits(2)-xLimits(1); yRange = yLimits(2)-yLimits(1); screenSize = get(groot,'Screensize')-[0 0 5 20]; % [1 1 width height] (minus tolerance for figure borders) imageAspectRatio = xRange/yRange; MegaPixelTarget = 100*10^6; % Category:Animated GIF files exceeding the 100 MP limit pxPerImage = MegaPixelTarget/nFrames; % pixel per gif frame ySize = sqrt(pxPerImage/imageAspectRatio); % gif height xSize = ySize*imageAspectRatio; % gif width xSize = floor(xSize); ySize = floor(ySize); % full pixels scaleReduction = min(...% repeat as often as possible for nice antialiasing floor(screenSize(4)/ySize), floor(screenSize(3)/xSize)); if scaleReduction == 0; error('"MegaPixelTarget" not possible; use smaller target or bigger monitor'); end % check figPos = [1 1 xSize*scaleReduction ySize*scaleReduction]; % big start image for antialiasing later [x y width height] set(figHandle, 'Position', figPos); if ~all(get(figHandle, 'Position') == figPos); error('figure Position could not be set'); end % check setXYlim(axesHandle,xLimits,yLimits); % set limits and drawnow; reducedRGBimage = uint8(ones(ySize,xSize,3,nFrames)); % allocate liSc = mean([xSize ySize]*scaleReduction)/350; % LineWidth scale; LineWidth is absolut, a bigger images needs thicker lines to keep them in proportion for iFrame = 1:nFrames cla(axesHandle) switch currentCase case 1 % animatedGears drawSpurWheel(center1,teeth(1),module(1),RGB.red ,liSc,RGB.black,-anglesFast(iFrame)); % left cogwheel (fast) drawSpurWheel(center2,teeth(2),module(2),RGB.blue ,liSc,RGB.black, anglesMedium(iFrame)); % cogwheel 2 (center) drawSpurWheel(center2,teeth(3),module(3),RGB.yellow,liSc,RGB.black, anglesMedium(iFrame)); % cogwheel 3 (center) drawSpurWheel(center3,teeth(4),module(4),RGB.green ,liSc,RGB.black,-anglesSlow(iFrame)); % right cogwheel (slow) case 2 % animated_3_Gear_Row drawSpurWheel(center1,teeth(1),module(1),RGB.red ,liSc,RGB.black,-anglesFast(iFrame)); % left cogwheel (fast) drawSpurWheel(center2,teeth(2),module(2),RGB.blue ,liSc,RGB.black, anglesMedium(iFrame)); % cogwheel 2 (center) drawSpurWheel(center3,teeth(3),module(3),RGB.green ,liSc,RGB.black,-anglesSlow(iFrame)); % right cogwheel (slow) case 3 % animated_internal gear drawSpurWheel(center1,teeth(1),module(1),RGB.red ,liSc,RGB.black,-anglesFast(iFrame)); % left cogwheel (fast) drawRingGear(teeth(2),module(2),RGB.green,liSc,RGB.black, -anglesSlow(iFrame)); case 4 % animated_rack_and_pinion drawSpurWheel(center1,teeth(1),module(1),RGB.red ,liSc,RGB.black,-anglesSlow(iFrame)); % left cogwheel (fast) drawRack(center2,teeth(2),module(2),RGB.green,liSc,RGB.black,-sideShift(iFrame),0); case {5,6,7,8} % animated_two_spur_gears drawSpurWheel(center1,teeth(1),module(1),RGB.red ,liSc,RGB.black,-anglesFast(iFrame)); % left cogwheel (fast) drawSpurWheel(center2,teeth(2),module(2),RGB.green ,liSc,RGB.black, anglesSlow(iFrame)); % right cogwheel (slow) end setXYlim(axesHandle,xLimits,yLimits); % reset limits and drawnow f = getframe(figHandle); reducedRGBimage(:,:,:,iFrame) = imReduceSize(f.cdata,scaleReduction); % allows subpixel lines if iFrame == 1 % SVG if ~isempty(which('plot2svg')) plot2svg(fullfile(pathstr, [saveName '_Frame1.svg']),figHandle) % by Juerg Schwizer else disp('plot2svg.m not available; see http://www.zhinst.com/blogs/schwizer/'); end end end switch currentCase % case 1 % animatedGears map = createImMap(reducedRGBimage,32,[RGB.bkgd; RGB.black; RGB.red; RGB.blue; RGB.yellow; RGB.green]); % colormap case 2 % animated_3_Gear_Row map = createImMap(reducedRGBimage,32,[RGB.bkgd; RGB.black; RGB.red; RGB.blue; RGB.green]); % colormap case 3 % animated_internal gear map = createImMap(reducedRGBimage,16,[RGB.bkgd; RGB.black; RGB.red; RGB.green]); % colormap case 4 % animated_rack_and_pinion map = createImMap(reducedRGBimage,16,[RGB.bkgd; RGB.black; RGB.red; RGB.green]); % colormap case {5,6,7,8} % animated_two_spur_gears map = createImMap(reducedRGBimage,16,[RGB.bkgd; RGB.black; RGB.red; RGB.green]); % colormap end im = uint8(ones(ySize,xSize,1,nFrames)); % allocate for iFrame = 1:nFrames im(:,:,1,iFrame) = rgb2ind(reducedRGBimage(:,:,:,iFrame),map,'nodither'); end imwrite(im,map,fullfile(pathstr, [saveName '.gif']),'DelayTime',1/60,'LoopCount',inf) % save gif disp([saveName '.gif has ' num2str(numel(im)/10^6 ,4) ' Megapixels']) % Category:Animated GIF files exceeding the 100 MP limit end function drawSpurWheel(center,toothNumber,module,fillC,linW,linC,startOffset) % DRAWSPURWHEEL - draw a simple Toothed Wheel % center: [x y] % toothNumber: scalar % module: scalar tooth "size" % fillC: color of filling [r g b] % linW: LineWidth % linC: LineColor % startOffset: start rotation (scalar)[rad] effectiveRadius = module*toothNumber/2; % effective Radius outsideRadius = effectiveRadius+1* module; % +---+ +---+ upperRisingRadius = effectiveRadius+0.5*module; % / \ / \ % effective Radius % / \ / \ lowerRisingRadius = effectiveRadius-0.5*module; % I I I I rootRadius = effectiveRadius-1.1*module; % + - - - + + - - - + + angleBetweenTeeth = 2*pi/toothNumber; % angle between 2 teeth angleOffPoints = (0:angleBetweenTeeth/16:(2*pi)); angleOffPoints = angleOffPoints+startOffset; % apply rotation offset angleOffPoints(7:16:end) = angleOffPoints(7:16:end) + 1/toothNumber^1.2; % hack to create smaller tooth tip angleOffPoints(11:16:end) = angleOffPoints(11:16:end) - 1/toothNumber^1.2; % hack to create smaller tooth tip angleOffPoints(8:16:end) = (angleOffPoints(7:16:end) + angleOffPoints(9:16:end))/2; % shift the neighbouring tip point in accordingly angleOffPoints(10:16:end) = (angleOffPoints(11:16:end) + angleOffPoints(9:16:end))/2; % shift the neighbouring tip point in accordingly angleOffPoints(6:16:end) = angleOffPoints(6:16:end) + 1/toothNumber^1.7; % hack to create slender upperRisingRadius angleOffPoints(12:16:end) = angleOffPoints(12:16:end) - 1/toothNumber^1.7; % hack to create slender upperRisingRadius radiusOffPoints = angleOffPoints; % allocate with correct site radiusOffPoints( 1:16:end) = rootRadius; % center bottom I radiusOffPoints( 2:16:end) = rootRadius; % left bottom I radiusOffPoints( 3:16:end) = rootRadius; % left bottom corner + radiusOffPoints( 4:16:end) = lowerRisingRadius; % lower rising bottom \ radiusOffPoints( 5:16:end) = effectiveRadius; % rising edge \ radiusOffPoints( 6:16:end) = upperRisingRadius; % upper rising edge \ radiusOffPoints( 7:16:end) = outsideRadius; % right top corner + radiusOffPoints( 8:16:end) = outsideRadius; % right top I radiusOffPoints( 9:16:end) = outsideRadius; % center top I radiusOffPoints(10:16:end) = outsideRadius; % left top I radiusOffPoints(11:16:end) = outsideRadius; % left top corner + radiusOffPoints(12:16:end) = upperRisingRadius; % upper falling edge / radiusOffPoints(13:16:end) = effectiveRadius; % falling edge / radiusOffPoints(14:16:end) = lowerRisingRadius; % lower falling edge / radiusOffPoints(15:16:end) = rootRadius; % right bottom corner + radiusOffPoints(16:16:end) = rootRadius; % right bottom I [X,Y] = pol2cart(angleOffPoints,radiusOffPoints); X = X+center(1); % center offset Y = Y+center(2); % center offset patch(X,Y,fillC,'EdgeColor',linC,'LineWidth',linW) % % effective Radius % [X,Y] = pol2cart(angleOffPoints,effectiveRadius); % X = X+center(1); % center offset % Y = Y+center(2); % center offset % plot(X,Y,'-.','Color',linC); %% shaft shaftRadius = module*6 /2; % small radius, assuming the effective radius a 6-tooth wheel would have [X,Y] = pol2cart(angleOffPoints,shaftRadius); X = X+center(1); % center offset Y = Y+center(2); % center offset plot(X,Y,'LineWidth',linW,'Color',linC); % plot(center(1),center(2),'.','Color',linC) function drawRingGear(toothNumber,module,fillC,linW,linC,startOffset) % DRAWRINGGEAR - draw a outer gear % center: [x y] % toothNumber: scalar % module: scalar tooth "size" % fillC: color of filling [r g b] % linW: LineWidth % linC: LineColor % startOffset: start rotation (scalar)[rad] effectiveRadius = module*toothNumber/2; % effective effectiveRadius outsideRadius = effectiveRadius-1* module; % +---+ +---+ upperRisingRadius = effectiveRadius-0.5*module; % / \ / \ % effective Radius % / \ / \ lowerRisingRadius = effectiveRadius+0.5*module; % I I I I rootRadius = effectiveRadius+1.1*module; % + - - - + + - - - + + angleBetweenTeeth = 2*pi/toothNumber; % angle between 2 teeth angleOffPoints = (0:angleBetweenTeeth/16:(2*pi)); angleOffPoints = angleOffPoints+startOffset; % apply rotation offset %% outerEdge maxRadius = rootRadius*1.2; % definition of outer line [Xout,Yout] = pol2cart(angleOffPoints,maxRadius); %% inner teeth radiusOffPoints = angleOffPoints; % init angleOffPoints( 7:16:end) = angleOffPoints(7:16:end) + 1/toothNumber^1.2; % hack to create smaller tooth tip angleOffPoints(11:16:end) = angleOffPoints(11:16:end) - 1/toothNumber^1.2; % hack to create smaller tooth tip angleOffPoints( 8:16:end) = (angleOffPoints(7:16:end) + angleOffPoints(9:16:end))/2; % shift the neighbouring tip point in accordingly angleOffPoints(10:16:end) = (angleOffPoints(11:16:end) + angleOffPoints(9:16:end))/2; % shift the neighbouring tip point in accordingly angleOffPoints( 6:16:end) = angleOffPoints(6:16:end) + 1/toothNumber^1.7; % hack to create slender tooth angleOffPoints(12:16:end) = angleOffPoints(12:16:end) - 1/toothNumber^1.7; % hack to create slender tooth radiusOffPoints( 1:16:end) = rootRadius; % center bottom I radiusOffPoints( 2:16:end) = rootRadius; % left bottom I radiusOffPoints( 3:16:end) = rootRadius; % left bottom corner + radiusOffPoints( 4:16:end) = lowerRisingRadius; % lower rising bottom \ radiusOffPoints( 5:16:end) = effectiveRadius; % rising edge \ radiusOffPoints( 6:16:end) = upperRisingRadius; % upper rising edge \ radiusOffPoints( 7:16:end) = outsideRadius; % right top corner + radiusOffPoints( 8:16:end) = outsideRadius; % right top I radiusOffPoints( 9:16:end) = outsideRadius; % center top I radiusOffPoints(10:16:end) = outsideRadius; % left top I radiusOffPoints(11:16:end) = outsideRadius; % left top corner + radiusOffPoints(12:16:end) = upperRisingRadius; % upper falling edge / radiusOffPoints(13:16:end) = effectiveRadius; % falling edge / radiusOffPoints(14:16:end) = lowerRisingRadius; % lower falling edge / radiusOffPoints(15:16:end) = rootRadius; % right bottom corner + radiusOffPoints(16:16:end) = rootRadius; % right bottom I [X,Y] = pol2cart(angleOffPoints,radiusOffPoints); [Xout,Yout] = poly2cw(Xout,Yout); [X, Y ] = poly2cw(X ,Y ); [Xb,Yb] = polybool('subtraction',Xout,Yout, X,Y); Xb = Xb(~isnan(Xb)); % notNaN Yb = Yb(~isnan(Yb)); % notNaN patch(Xb,Yb,fillC,'EdgeColor','none') plot(X, Y, 'LineWidth',linW,'Color',linC); % draw teeth outline plot(Xout,Yout,'LineWidth',linW,'Color',linC); % draw outer circle function drawRack(center,toothNumber,module,fillC,linW,linC,startOffset,top) % center: [x y] % toothNumber: scalar % module: scalar tooth "size" % fillC: color of filling [r g b] % linW: LineWidth % linC: LineColor % startOffset: initial shift % top: 1=top 0=bottom x = (0:toothNumber*4-2)*pi*module/4; x = x-mean(x)+center(1)+startOffset; y = zeros(size(x)); y(1:4:end) = y(1:4:end)+1.1*module; % +###I bottom y(2:4:end) = y(2:4:end)-1 *module; % +######I tip y(3:4:end) = y(3:4:end)-1 *module; % +######I tip y(4:4:end) = y(4:4:end)+1.1*module; % +###I bottom x(1:4:end) = x(1:4:end)-0.14*module; % bottom smaller x(2:4:end) = x(2:4:end)+0.14*module; % tip smaller x(3:4:end) = x(3:4:end)-0.14*module; % tip smaller x(4:4:end) = x(4:4:end)+0.14*module; % bottom smaller x = [x(1) x x(end)]; y = [5*module y 5*module]; if ~top y = -y; % flip end y = y+center(2); patch(x,y,fillC,'EdgeColor',linC,'LineWidth',linW); function setXYlim(axesHandle,xLimits,yLimits) % set limits; practically the axis overhangs the figure all around, to % hide rendering error at line-ends. % Input: % axesHandle: % xLimits, yLimits: [min max] overh = 0.05; % 5% overhang all around; 10% bigger in x and y xlim([+xLimits(1)*(1+overh)-xLimits(2)*overh -xLimits(1)*overh+xLimits(2)*(1+overh)]) ylim([+yLimits(1)*(1+overh)-yLimits(2)*overh -yLimits(1)*overh+yLimits(2)*(1+overh)]) set(axesHandle,'Position',[-overh -overh 1+2*overh 1+2*overh]); % stretch axis as bigger as figure, [x y width height] drawnow; function im = imReduceSize(im,redSize) % Input: % im: image, [imRows x imColumns x nChannel x nStack] (unit8) % imRows, imColumns: must be divisible by redSize % nChannel: usually 3 (RGB) or 1 (grey) % nStack: number of stacked images % usually 1; >1 for animations % redSize: 2 = half the size (quarter of pixels) % 3 = third the size (ninth of pixels) % ... and so on % Output: % im: [imRows/redSize x imColumns/redSize x nChannel x nStack] (unit8) % % an alternative is : imNew = imresize(im,1/scaleReduction ,'bilinear'); % BUT 'bicubic' & 'bilinear' produces fuzzy lines % IMHO this function produces nicer results as "imresize" [nRow,nCol,nChannel,nStack] = size(im); if redSize==1; return; end % nothing to do if redSize~=round(abs(redSize)); error('"redSize" must be a positive integer'); end if rem(nRow,redSize)~=0; error('number of pixel-rows must be a multiple of "redSize"'); end if rem(nCol,redSize)~=0; error('number of pixel-columns must be a multiple of "redSize"'); end nRowNew = nRow/redSize; nColNew = nCol/redSize; im = double(im).^2; % brightness rescaling from "linear to the human eye" to the "physics domain"; see youtube: /watch?v=LKnqECcg6Gw im = reshape(im, nRow, redSize, nColNew*nChannel*nStack); % packets of width redSize, as columns next to each other im = sum(im,2); % sum in all rows. Size of result: [nRow, 1, nColNew*nChannel] im = permute(im, [3,1,2,4]); % move singleton-dimension-2 to dimension-3; transpose image. Size of result: [nColNew*nChannel, nRow, 1] im = reshape(im, nColNew*nChannel*nStack, redSize, nRowNew); % packets of width redSize, as columns next to each other im = sum(im,2); % sum in all rows. Size of result: [nColNew*nChannel, 1, nRowNew] im = permute(im, [3,1,2,4]); % move singleton-dimension-2 to dimension-3; transpose image back. Size of result: [nRowNew, nColNew*nChannel, 1] im = reshape(im, nRowNew, nColNew, nChannel, nStack); % putting all channels (rgb) back behind each other in the third dimension im = uint8(sqrt(im./redSize^2)); % mean; re-normalize brightness: "scale linear to the human eye"; back in uint8 function map = createImMap(imRGB,nCol,startMap) % createImMap creates a color-map including predefined colors. % "rgb2ind" creates a map but there is no option to predefine some colors, % and it does not handle stacked images. % Input: % imRGB: image, [imRows x imColumns x 3(RGB) x nStack] (unit8) % nCol: total number of colors the map should have, [integer] % startMap: predefined colors; colormap format, [p x 3] (double) imRGB = permute(imRGB,[1 2 4 3]); % step1; make unified column-image (handling possible nStack) imRGBcolumn = reshape(imRGB,[],1,3,1); % step2; make unified column-image fullMap = double(permute(imRGBcolumn,[1 3 2]))./255; % "column image" to color map [fullMap,~,imMapColumn] = unique(fullMap,'rows'); % find all unique colors; create indexed colormap-image % "cmunique" could be used but is buggy and inconvenient because the output changes between "uint8" and "double" nColFul = size(fullMap,1); nColStart = size(startMap,1); disp(['Number of colors: ' num2str(nColFul) ' (including ' num2str(nColStart) ' self defined)']); if nCol<=nColStart; error('Not enough colors'); end if nCol>nColFul; warning('More colors than needed'); end isPreDefCol = false(size(imMapColumn)); % init for iCol = 1:nColStart diff = sum(abs(fullMap-repmat(startMap(iCol,:),nColFul,1)),2); % difference between a predefined and all colors [mDiff,index] = min(diff); % find matching (or most similar) color if mDiff>0.05 % color handling is not precise warning(['Predefined color ' num2str(iCol) ' does not appear in image']) continue end isThisPreDefCol = imMapColumn==index; % find all pixel with predefined color disp([num2str(sum(isThisPreDefCol(:))) ' pixel have predefined color ' num2str(iCol)]); isPreDefCol = or(isPreDefCol,isThisPreDefCol); % combine with overall list end [~,mapAdditional] = rgb2ind(imRGBcolumn(~isPreDefCol,:,:),nCol-nColStart,'nodither'); % create map of remaining colors map = [startMap;mapAdditional];