Getting RMagick/ImageMagick gravity with text - ruby

Here is Ruby code:
require 'rmagick'
include Magick
img = Image.new(300, 300)
draw = Draw.new
draw.line(0, 150, 300, 150)
draw.line(150, 0, 150, 300)
# for each of known gravity constants...
%w[NorthWestGravity NorthGravity NorthEastGravity WestGravity CenterGravity EastGravity SouthWestGravity SouthGravity SouthEastGravity].
each{|g|
# set gravity to this value...
draw.gravity Magick.const_get(g)
# ...and draw text with this constant name
draw.text 150, 150, g
}
draw.draw(img)
img.write('tmp/gravity.png')
Here is image which it produces:
For SouthEast/NorthWest and similar gravities result is as expected (text is near 150,150, moved in desired direction). But for South, North and others result is really pretty weird.
As far as I can understand from code, RMagick just translates gravity and text commands into corresponding ImageMagick drawing primitives, so, I suppose its something in ImageMagick's gravity concept that I can't get.
What is it?..

I suppose its something in ImageMagick's gravity concept that I can't get.
What is it?..
The key to understanding what's going on is to locate the CenterGravity text.
Shifted left by 150px, and down by 150px.
Now compare compare NorthWestGravity position.
Also translated left & down by 150px respectively. Seeing a trend?
Your issue is with this line...
draw.text 150, 150, g
The Magick::Draw API maps to MVG spec. Use Magick::Draw.push & Magick::Draw.pop to control drawing context.
Edit from comments...
For setting the origin of text to be drawing, you'll need to calculate the position after evaluation the text/type metrics.
Example.
require 'rmagick'
include Magick
img = Image.new(300, 300) {
self.background_color = "palegreen"
}
draw = Draw.new
dotes = Draw.new # Dotes added for point of origin
dotes.fill = "red"
cursor = 1
# for each of known gravity constants...
%w[NorthWestGravity NorthGravity NorthEastGravity WestGravity CenterGravity EastGravity SouthWestGravity SouthGravity SouthEastGravity].
each{|g|
offsetX = 150
offsetY = cursor * 25
dotes.circle offsetX, offsetY, offsetX+2, offsetY+2
# Get metrics of text
metrics = draw.get_type_metrics(img, g)
# Full width
if %w[NorthWestGravity WestGravity SouthWestGravity].include? g then
offsetX -= metrics[:width]
end
# Full height
if %w[SouthWestGravity SouthGravity SouthEastGravity].include? g then
offsetY += metrics[:ascent]
end
# Half width
if %w[NorthGravity SouthGravity CenterGravity].include? g then
offsetX -= metrics[:width] / 2
end
# Half height
if %w[WestGravity CenterGravity EastGravity].include? g then
offsetY += metrics[:ascent] / 2
end
draw.text offsetX, offsetY, g
cursor += 1
}
dotes.draw(img)
draw.draw(img)
img.write('output.png')

Related

Format Data Label as Percentage and Move it next to the other Data Label

I'm new here and also a beginner Python user. In this chart I was trying to make, I just need to format the blue labels into percentage with 2 decimal places and move them right next to the values at the end of the bars.
Here is the code:
index = np.arange(6)
fig, ax = plt.subplots(figsize=(10, 2))
list1x = list(dict(train['Outlet_Location_Type'].value_counts()).keys())
list1y = list(train['Outlet_Location_Type'].value_counts())
ax.barh(list1x, list1y, alpha=0.7,
# width = 0.5,
color=cm.Blues([i / 0.00525 for i in [ 0.00808, 0.0045, 0.00281]])
)
plt.rcParams.update({'font.size': 10})
rects = ax.patches
for i, label in enumerate(ii / 8325 * 100 for ii in list1y):
ax.text(label, i, str(label), size=10, ha='left', va='center', color = "blue")
for h, label in enumerate(list1y):
ax.text(label, h, label, size=10, ha='left', va='center')
ax.text(0, 1.02, 'Outlet Location Type Count', transform=ax.transAxes, size=12, weight=600, color='#777777')
ax.xaxis.set_ticks_position('bottom')
ax.tick_params(axis='x', colors='black', labelsize=9)
ax.set_axisbelow(True)
plt.xticks([])
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.show()
I think there could be data type issue causing the blue values to stay at the left side. I can't find the formatting solution to change them into percentage.
Thank you so much in advance!

love2d connecting line segment objects to make a pencil tool that draws lines

I'm trying to create a game that is similar to line rider. I've kind of programmed a pencil right now except that the lines are scattered objects.
I want to be able to connect those red dots to make a smooth line so that the ball can roll across it.
It should more look like this
This is my code
function love.load()
printx = 0 --position to be drawn on when the mouse is pressed
printy = 0 --position to be drawn on when the mouse is pressed
love.physics.setMeter(64) --the height of a meter our worlds will be 64px
world = love.physics.newWorld(0, 9.81*64, true) --The world the everything exists in. Horizontal gravity=0. Bertical gravity=9.81. True says that the world is allowed to sleep.
objects = {} -- table to hold all our physical objects
--let's create the ground
objects.lines = {}
objects.ground = {}
objects.ground.body = love.physics.newBody(world, 650/2, 650-50/2)
objects.ground.shape = love.physics.newRectangleShape(650, 50) --make a rectangle with a width of 650 and a height of 50
objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape); --attach shape to body
--let's create a ball
objects.ball = {}
objects.ball.body = love.physics.newBody(world, 650/2, 600, "dynamic") --Determines where the object will start. In the center of the world and dynamic(moves around)
objects.ball.shape = love.physics.newCircleShape(20) --the ball's shape has a radius of 20
objects.ball.fixture = love.physics.newFixture(objects.ball.body, objects.ball.shape, 1) -- Attach fixture to body and give it a density of 1.
--initial graphics setup
love.graphics.setBackgroundColor(0.41, 0.53, 0.97) --set the background color to a nice blue
love.window.setMode(650, 650) --set the window dimensions to 650 by 650
end
function love.update(dt)
world:update(dt) --this puts the world into motion
if love.mouse.isDown(1) then
local printx = love.mouse.getX()
local printy = love.mouse.getY()
line = {}
line.body = love.physics.newBody(world, printx, printy, "static")
line.shape = love.physics.newRectangleShape(0, 0, 5, 5)
line.fixture = love.physics.newFixture(line.body, line.shape, 5) -- A higher density gives it more mass.
table.insert(objects.lines, line)
love.graphics.setColor(50,50,50)
love.graphics.polygon("fill", line.body:getWorldPoints(line.shape:getPoints()))
end
end
function love.draw()
love.graphics.setColor(0.28, 0.63, 0.05) -- set the drawing color to green for the ground
love.graphics.polygon("fill", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- draw a "filled in" polygon using the ground's coordinates
love.graphics.setColor(0.76, 0.18, 0.05) --set the drawing color to red for the ball
love.graphics.circle("fill", objects.ball.body:getX(), objects.ball.body:getY(), objects.ball.shape:getRadius())
for _, block in pairs(objects.lines) do
love.graphics.polygon("fill", block.body:getWorldPoints(block.shape:getPoints()))
end
end
Within love.update
if not love.mouse.isDown(1) then
oldx = nil
oldy = nil
end
--draw lines when the mouse is down
if love.mouse.isDown(1) then
local printx = love.mouse.getX() + Camera.x --x coordinate of the mouse
local printy = love.mouse.getY() + Camera.y --y coordinate of the mouse
if oldx ~= nil then
line = {}
line.x1 = oldx
line.x2 = printx
line.y1 = oldy
line.y2 = printy
line.body = love.physics.newBody(world, 0, 0, "static")
line.shape = love.physics.newEdgeShape(printx, printy, oldx, oldy)
line.fixture = love.physics.newFixture(line.body, line.shape, 5)
table.insert(objects.lines, line)
end
oldx = printx
oldy = printy
end
Within love.draw()
for _, line in pairs(objects.lines) do
love.graphics.line( line.x1 - Camera.x, line.y1 - Camera.y, line.x2 - Camera.x, line.y2 - Camera.y) --:getWorldPoints(block.shape:getPoints()))
end

Zooming/scaling a tiled image anchoring the zoom point to the mouse cursor

I've got a project where I'm designing an image viewer for tiled images. Every image tile is 256x256 pixels. For each level of scaling, I'm increasing the size of each image by 5%. I represent the placement of the tiles by dividing the screen into tiles the same size as each image. An offset is used to precicely place each image where needed. When the scaling reaches a certain point(1.5), I switch over to a new layer of images that altogether has a greater resolution than the previous images. The zooming method itself looks like this:
def zoomer(self, mouse_pos, zoom_in): #(tuple, bool)
x, y = mouse_pos
x_tile, y_tile = x / self.tile_size, y / self.tile_size
old_scale = self.scale
if self.scale > 0.75 and self.scale < 1.5:
if zoom_in:
self.scale += SCALE_STEP # SCALE_STEP = 5% = 0.05
ratio = (SCALE_STEP + 1)
else:
self.scale -= SCALE_STEP
ratio = 1 / (SCALE_STEP + 1)
else:
if zoom_in:
self.zoom += 1
self.scale = 0.8
ratio = (SCALE_STEP + 1)
else:
self.zoom -= 1
self.scale = 1.45
ratio = 1 / (SCALE_STEP + 1)
# Results in x/y lengths of the relevant full image
x_len = self.size_list[self.levels][0] / self.power()
y_len = self.size_list[self.levels][1] / self.power()
# Removing extra pixel if present
x_len = x_len - (x_len % 2)
y_len = y_len - (y_len % 2)
# The tile's picture coordinates
tile_x = self.origo_tile[0] + x_tile
tile_y = self.origo_tile[1] + y_tile
# The mouse's picture pixel address
x_pic_pos = (tile_x * self.tile_size) -
self.img_x_offset + (x % self.tile_size)
y_pic_pos = (tile_y * self.tile_size) -
self.img_y_offset + (y % self.tile_size)
# Mouse percentile placement within the image
mouse_x_percent = (x_pic_pos / old_scale) / x_len
mouse_y_percent = (y_pic_pos / old_scale) / y_len
# The mouse's new picture pixel address
new_x = (x_len * self.scale) * mouse_x_percent
new_y = (y_len * self.scale) * mouse_y_percent
# Scaling tile size
self.tile_size = int(TILE_SIZE * self.scale)
# New mouse screen tile position
new_mouse_x_tile = x / self.tile_size
new_mouse_y_tile = y / self.tile_size
# The mouse's new tile address
new_tile_x = new_x / self.tile_size
new_tile_y = new_y / self.tile_size
# New tile offsets
self.img_x_offset = (x % self.tile_size) - int(new_x % self.tile_size)
self.img_y_offset = (y % self.tile_size) - int(new_y % self.tile_size)
# New origo tile
self.origo_tile = (int(new_tile_x) - new_mouse_x_tile,
int(new_tile_y) - new_mouse_y_tile)
Now, the issue arising from this is that the mouse_.._percent variables never seem to match up with the real position. For testing purposes, I feed the method with a mouse position centered in the middle of the screen and the picture centered in the middle too. As such, the resulting mouse_.._percent variable should, in a perfect world, always equal 50%. For the first level, it does, but quickly wanders off when scaling. By the time I reach the first zoom breakpoint (self.scale == 1.5), the position has drifted to x = 48%, y = 42%.
The self.origo_tile is a tuple containing the x/y coordinate for the tile to be drawn on screen tile (0, 0)
I've been staring at this for hours, but can't seen to find a remedy for it...
How the program works:
I apologize that I didn't have enough time to apply this to your code, but I wrote the following zooming simulator. The program allows you to zoom the same "image" multiple times, and it outputs the point of the image that would appear in the center of the screen, along with how much of the image is being shown.
The code:
from __future__ import division #double underscores, defense against the sinister integer division
width=256 #original image size
height=256
posx=128 #original display center, relative to the image
posy=128
while 1:
print "Display width: ", width
print "Display height: ", height
print "Center X: ", posx
print "Center Y: ", posy
anchx = int(raw_input("Anchor X: "))
anchy = int(raw_input("Anchor Y: "))
zmag = int(raw_input("Zoom Percent (0-inf): "))
zmag /= 100 #convert from percent to decimal
zmag = 1/zmag
width *= zmag
height *= zmag
posx = ((anchx-posx)*zmag)+posx
posy = ((anchy-posy)*zmag)+posy
Sample output:
If this program outputs the following:
Display width: 32.0
Display height: 32.0
Center X: 72.0
Center Y: 72.0
Explanation:
This means the zoomed-in screen shows only a part of the image, that part being 32x32 pixels, and the center of that part being at the coordinates (72,72). This means on both axes it is displaying pixels 56 - 88 of the image in this specific example.
Solution/Conclusion:
Play around with that program a bit, and see if you can implement it into your own code. Keep in mind that different programs move the Center X and Y differently, change the program I gave if you do not like how it works already (though you probably will, it's a common way of doing it). Happy Coding!

Matlab: Something like "relative" position with uicontrol/axis; keep fixed margins when resizing

I currently have a big headache to get a small GUI working nicely which isn't being created with GUI editor but programmatically! What I have so far is something like the following:
hFig = figure();
set(hFig, 'Position', [300 200 500 400]);
plot((1:10).^2, '*-r');
% Größe des Plots so anpassen, dass links Platz für Buttons
ap = get(gca, 'TightInset');
fp = get(gcf, 'Position');
set(gca, 'Position', [160/fp(3), 30/fp(4), (fp(3)-180)/fp(3), (fp(4)-60)/fp(4)]);
uicontrol('Style', 'pushbutton', 'String', 'foo', 'Position', [15 fp(4)-60 110 30]);
uicontrol('Style', 'pushbutton', 'String', 'bar', 'Position', [15 fp(4)-100 110 30]);
Try to resize it: It doesn't 'look' the same, which means that the uicontrol boxes don't stay at the same relative position and the margins from the axis to the figure window get bigger. What I want to achieve is:
Have a figure window with a given position (x/y, width and height) with a plot inside. The plot will have a title and labels for x and y. Make the plot as height and width to have the TightInset plus a margin in each direction of a certain px-size (e.g. TightInset + 10px) as big as the figure window; except leave 150px of free space on the left to place some uicontrol buttons, and have them stay in the same position: This would be the same as being able to give the position from top/left (top = 20, left = 10) instead of bottom/left.
Thanks a lot!
Okay finally found a working solution I wanted it to be :-) Hopefully it is helpfull for somebody interested in it:
Main script file:
p = [300 300 1000 600];
fixedMargins = [250 0 0 0]; % [left, top, right, bottom]
f = figure('Position', p, 'Color', [0.9 0.9 0.9]);
plot(-10:10, (-10:10).^3, '*-r');
set(f, 'ResizeFcn', {#resizeCallback, gca, fixedMargins, {#myuiFunc, f, 40, 50}});
title('bla')
xlabel('foooooooooo')
ylabel('barrrrrrr')
Resize Callback Function:
% Need to pass the handle of the axis to modify (hAx) AND to pass the
% desired margins as second extra callback argument:
% [left, top, right, bottom]!
function resizeCallback(hFig, ~, hAx, fixedMargins, func)
% Disable automatic rezising
set(hAx, 'Units', 'pixels');
% Figure-Size
fp = get(hFig, 'Position');
% Calculate Position of the axis
margin = get(hAx, 'TightInset') * [-1 0 1 0; 0 -1 0 1; 0 0 1 0; 0 0 0 1];
% Position to fill the figure minus the TightInset-Margin
newPos = [0 0 fp(3:4)] - margin;
% Change position based on margins
newPos(1) = newPos(1) + fixedMargins(1);
newPos(3) = newPos(3) - fixedMargins(1) - fixedMargins(3);
newPos(2) = newPos(2) + fixedMargins(4);
newPos(4) = newPos(4) - fixedMargins(2) - fixedMargins(4);
% Set new position
set(hAx, 'Position', newPos);
% Call UI-Func
if(nargin == 5)
f = func{1};
args = func(2:end);
f(args{:});
end
end
You can pass whatever function you want to be called when resizing the figure window, e.g. to update something in the figure. In my example it's the myuiFunc(), which is the following:
function myuiFunc(hFig, left, top)
persistent handles;
if(~isempty(handles))
delete(handles);
handles = [];
end
fp = get(hFig, 'Position');
h1 = uicontrol('Style', 'pushbutton', 'String', 'Foo','Position', [left fp(4)-top 100 35]);
h2 = uicontrol('Style', 'pushbutton', 'String', 'Bar','Position', [left fp(4)-top-50 100 35]);
handles = [h1 h2];
end
I like it :) Hopefully you too!
Edit: No need to edit the resizeCallback Function! Should work if you just pass your desired margins to it and if you like, additionally a function handle with arguments which will be called for each resize!
You can also use "Normalized" units.
uicontrol('Style', 'pushbutton', 'String', 'foo', 'Units','normalized','Position', [0.90 0.05 0.08 0.08] );

What's a more elegant rephrasing of this cropping algorithm? (in Python)

I want to crop a thumbnail image in my Django application, so that I get a quadratic image that shows the center of the image. This is not very hard, I agree.
I have already written some code that does exactly this, but somehow it lacks a certain ... elegance. I don't want to play code golf, but there must be a way to express this shorter and more pythonic, I think.
x = y = 200 # intended size
image = Image.open(filename)
width = image.size[0]
height = image.size[1]
if (width > height):
crop_box = ( ((width - height)/2), 0, ((width - height)/2)+height, height )
image = image.crop(crop_box)
elif (height > width):
crop_box = ( 0, ((height - width)/2), width, ((height - width)/2)+width )
image = image.crop(crop_box)
image.thumbnail([x, y], Image.ANTIALIAS)
Do you have any ideas, SO?
edit: explained x, y
I think this should do.
size = min(image.Size)
originX = image.Size[0] / 2 - size / 2
originY = image.Size[1] / 2 - size / 2
cropBox = (originX, originY, originX + size, originY + size)
The fit() function in the PIL ImageOps module does what you want:
ImageOps.fit(image, (min(*image.size),) * 2, Image.ANTIALIAS, 0, (.5, .5))
width, height = image.size
if width > height:
crop_box = # something 1
else:
crop_box = # something 2
image = image.crop(crop_box)
image.thumbnail([x, x], Image.ANTIALIAS) # explicitly show "square" thumbnail
I want to a content analysis of a jepg image. I wish to take a jpeg imafe say 251 x 261 and pass it through an algorithm to crop it to say 96 x 87. Can this program do that like t write an intelligent cropping algorithm, with a prompt to rezie the image.

Resources