Daily Archives: November 23, 2015

Coordinate systems

An FMX file contains coordinates like x-pos and y-pos, width and height of components (canvas, buttons, graphical elements like rectangle or arc). The compiled format doesn’t store different values for every coordinate, if we change the coordinate system from pixels to dots or inches to centimeters. The numeric value is stable, there must be a factor, which differs for every unit.

Let’s take a look to the values of a rectangle. We have x, y, width and height (and we have the same tuple for the border description). The line thickness will be pre-calculated into coordinates.

X = 9102
Y = 4324
Width = 16270
Height = 5689

The coordinates in the Form Builder were (for coordinate system DOTs)

80
38
143
50

How I can translate the large values into the Dot coordinates? This question has engaged me for two days. First, the values aren’t Oracle Numbers, they have a stable size of 4 bytes. Next I have divided both values:

9102 / 80 = 113.775
4324 / 38 = 113.789
16270 / 143 = 113.776
5689 / 50 = 113.78

This seems to be a static result (except little rounding errors). If I switch to another coordinate system, I’ll get another static result (e.g. for PIXELS it will be 85,333). These values seem not to be a DPI, I haven’t seen such a value with a fraction part. But how can I calculate the values? In the Oracle documentation you can find a short sentence about the DOTs coordinate system: every dot is a 1/72 of an inch. But I cannot convert 1/72 into 113.something …

A lot of tests later I have seen a well-known value: a width coordinate for one inch has a value of 8192, ha! Let’s try:

9102 / 8192 = 1.1111 * 72 = 79.998
4324 / 8192 = 0.5278 * 72 = 38.004
16270 / 8192 = 1.9861 * 72 = 142.998
5689 / 8192 = 0.6944 * 72 = 50.000

Perfect match! I can use Math.round(float val) to get the right Form Builder coordinates. The other coordinate systems have some other factors (pixel = 96, centimeters = 2.54, inches = 1, decimal = 720). But you have always to use 8192 in the formula. I’ll use an Java enum to handle the translation:

public enum CoordSystemUnit {

	PIXEL(0x2) {
		public float convert(int coord) {
			float factor = (8192f / 1000f) / 96f;
			return Math.round(coord / factor) * 1000f;
		}
	},
	INCH(0x3) {
		public float convert(int coord) {
			float factor = (8192f / 1000f) / 1f;
			return Math.round(coord / factor) * 1000f;
		}
	},
	CENTIMETER(0x5) {
		public float convert(int coord) {
			float factor = (8192f / 1000f) / 2.54f;
			return Math.round(coord / factor) * 1000f;
		}
	},
	DOT(0x7) {
		public float convert(int coord) {
			float factor = (8192f / 1000f) / 72f;
			return Math.round(coord / factor) * 1000f;
		}
	},
	DECIMAL(0xC) {
		public float convert(int coord) {
			float factor = (8192f / 1000f) / 720f;
			return Math.round(coord / factor) * 1000f;
		}
	};

	private final int unit;

	private CoordSystemUnit(int unit) {
		this.unit = unit;
	}

	public static CoordSystemUnit lookup(int unit) {
		for (CoordSystemUnit t : CoordSystemUnit.values()) {
			if (t.unit == unit) {
				return t;
			}
		}
		throw new IllegalArgumentException("Unknown CoordSystemUnit " + unit);
	}

	public abstract float convert(int coord);
}