CMSC 498B: Developing User Interfaces - Spring 2002Basic 2D Graphics | |
2D Graphics and User InterfacesWhy graphics first?Everything on the screen is graphics. Everything. Without understanding how things get drawn on the screen, one can not understand how interfaces are created, and one can not create new kinds of interfaces at all.
The drawing abstractionEvery computer graphics system (i.e., operating system) offers some notion of a canvas to draw onto. Usually, the canvases are separated into windows that are distinct from each other, which provide a relative coordinate system and isolation. This abstraction allows the same binary-level application to render onto different kinds of surfaces such as screens, off-screen buffers (i.e., clipboards), and printers, plotters, and direct neural implants... Most current systems offer a a resolution-independent (or device-independent) API. This is crucial. If all rendering was done in pixels, a 100 pixel rectangle would be about an inch big on most displays, but would be less than 1/10th of an inch big on some printers. Java offers the Graphics/Graphics2D classes that present this abstraction, and C# offers a Graphics class as well. coordinatesDevice coordinates - Coordinates of the physical device:
Window coordinates - Coordinates within an operating system window
Physical coordinates ("device-independent coords") - correspond to physical measurements
Model ("local") coordinates - correspond to the object you are defining
Transformations
Graphics description modelsStroke model describes images with strokes of specified color and thickness. There are also several other parameters, such as how the line segments are connected, and whether the line is drawn solid, or with dashes. In addition, strokes can be antialiased. Line from (4,5) to (9,7) in red with a thickness of 5 Circle centered at (19,8) in blue with a radius of 8 and a thickness of 3 ...
Antialiasing a line
Region model describes images with filled areas such as arcs, text, splines, and other shapes - often other stroke objects. The area may be filled with a color, or a more complex fill such as a gradient or texture. Polygon filling (0, 0)-(10, 5)-(5, 10) with yellow ... Solid fill: Gradient fill: Pixel model describes images as a discrete number of pixels. Each pixel is specified by a color from one of several possible color models. In practice, graphics are described with a combination of stroke, region, and pixel descriptions. Graphics objectsGraphics object (device context) provides an interface between the application program and the display device. An uniform set of graphics primitives is provided for very different devices: display screen, printer, image file, ... In Java AWT (abstract windowing toolkit) the Graphics object is an
instance of the class public void paint(Graphics g) { g.setColor(Color.red); g.fillRect(10, 10, 100, 200); Graphics2D g2 = (Graphics2D)g; // g is actually an instance of a Graphics2D g2.draw(...); } In C#, it is about the same, but there is only a single Graphics class, and you access it through another class. protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.DrawString("Hello World!", font, blackBrush, 100, 100); } In both cases, Graphics holds state that prescribes how future Graphics method calls will actually behave. There is a difference in Java & C# between what state Graphics holds, and what values are passed in as parameters to the render methods. Example: Java Graphics has Color state, and uses a Color for stroke and fill (of Graphics), and uses a Paint for fill (of Graphics2D) g.setColor(Color.red); g.drawLine(0, 5, 10, 15); g.drawLine(50, 50, 30, 20); C# Graphics does not have Color state, but instead requires Pen and Brush objects passed as parameters Pen redPen = new Pen(Color.Red); g.DrawLine(redPen, 0, 5, 10, 15); g.DrawLine(redPen, 50, 50, 30, 20); What are the tradeoffs between these two approaches? Which state is stored in each kind of Graphics? How to do it/* * A Java program that creates a custom component with its own renderer. * * Ben Bederson, January 29, 2002 */ import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; public class BasicGraphics extends JFrame { static public void main(String[] args) { new BasicGraphics(); } public BasicGraphics() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); BCanvas canvas = new BCanvas(); canvas.setPreferredSize(new Dimension(400, 400)); getContentPane().add(canvas); pack(); setVisible(true); } } class BCanvas extends JComponent { public void paintComponent(Graphics graphics) { Graphics2D g2 = (Graphics2D) graphics; g2.setColor(Color.white); g2.fillRect(getX(), getY(), getWidth(), getHeight()); g2.setFont(new Font("Helvetica", Font.PLAIN, 15)); g2.setColor(Color.black); g2.drawString("Hello World!", 100, 100); } } class StrokeCanvas extends JComponent { public void paintComponent(Graphics graphics) { Graphics2D g2 = (Graphics2D) graphics; g2.setColor(Color.white); g2.fillRect(getX(), getY(), getWidth(), getHeight()); Stroke stroke = new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g2.setStroke(stroke); g2.setColor(Color.red); Rectangle2D rect = new Rectangle2D.Double(10, 10, 100, 100); Line2D line = new Line2D.Double(50, 50, 200, 100); g2.draw(rect); g2.draw(line); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Stroke stroke2 = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2.setStroke(stroke2); Line2D line2 = new Line2D.Double(200, 50, 50, 100); g2.draw(line2); } } class RegionCanvas extends JComponent { public void paintComponent(Graphics graphics) { Graphics2D g2 = (Graphics2D) graphics; g2.setColor(Color.white); g2.fillRect(getX(), getY(), getWidth(), getHeight()); Paint paint = Color.blue; g2.setPaint(paint); Rectangle2D rect = new Rectangle2D.Double(10, 10, 100, 100); g2.fill(rect); Paint paint2 = new GradientPaint(100, 100, Color.red, 200, 200, Color.blue); Rectangle2D rect2 = new Rectangle2D.Double(100, 100, 100, 100); g2.setPaint(paint2); g2.fill(rect2); } } /* * A Java program that creates a custom component with its own renderer, * and demonstrates how to load and render images. * * Ben Bederson, January 29, 2002 */ import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; public class BasicImage extends JFrame { static public void main(String[] args) { new BasicImage(); } public BasicImage() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ImageCanvas canvas = new ImageCanvas("dana.jpg"); canvas.setPreferredSize(new Dimension(400, 400)); getContentPane().add(canvas); pack(); setVisible(true); } } class ImageCanvas extends JComponent { Image image = null; public ImageCanvas(String fileName) { loadImage(fileName); } public void loadImage(String fileName) { image = Toolkit.getDefaultToolkit().createImage(fileName); MediaTracker tracker = new MediaTracker(this); tracker.addImage(image, 0); try { tracker.waitForID(0); } catch (InterruptedException exception) { System.out.println("Couldn't load image: " + fileName); } } public void paintComponent(Graphics graphics) { Graphics2D g2 = (Graphics2D) graphics; g2.setColor(Color.white); g2.fillRect(getX(), getY(), getWidth(), getHeight()); g2.drawImage(image, 50, 50, this); } } /* * A C# program that creates a custom component with its own renderer. * * Ben Bederson, January 30, 2002 */ using System; using System.Drawing; using System.Windows.Forms; public class BasicGraphics : System.Windows.Forms.Form { static public void Main() { Application.Run(new BasicGraphics()); } public BasicGraphics() { Size = new Size(400, 400); StartPosition = FormStartPosition.CenterScreen; BasicControl control = new BasicControl(); control.Location = new Point(0, 0); control.Size = new Size(400, 400); Controls.Add(control); } } class BasicControl : Control { static Brush blackBrush = new SolidBrush(Color.Black); static Font font = new Font("Arial", 15); protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; BackColor = Color.White; g.DrawString("Hello World!", font, blackBrush, 100, 100); } } class StrokeControl : Control { static Pen redPen = new Pen(Color.Red); protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; BackColor = Color.White; redPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round; redPen.StartCap = System.Drawing.Drawing2D.LineCap.Round; redPen.EndCap = System.Drawing.Drawing2D.LineCap.Round; redPen.Width = 10; g.DrawRectangle(redPen, 10, 10, 100, 100); g.DrawLine(redPen, 50, 50, 200, 100); redPen.StartCap = System.Drawing.Drawing2D.LineCap.Flat; redPen.EndCap = System.Drawing.Drawing2D.LineCap.Flat; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.DrawLine(redPen, 200, 50, 50, 100); } } Drawing shapesJava Graphics has basic shape support: lines, ellipses, polygons, etc. Java Graphics2D also has generic Shape class with g2.draw() and g2.fill() methods. See java.awt.geom. Lots of Shape subclasses. Including GeneralPath which connects points w/ lines or curves. It can be rendered resolution-independent, or can be flattened with FlatteningPathIterator C# has fixed shapes rather than a generic Shape class Rendering - Damage/RedrawOverride the appropriate paint method of your canvas. Render when you are asked to. Request to be rendered with JComponent.repaint() (Java) or Control.Invalidate (C#). Remember that these only request repaints to happen in the future, they do not happen immediately. Or, just repaint a portion of the canvas - repaint(x, y, w, h) (Java) or Invalidate(Rectangle) C# Be careful to not to request a repaint within a paint method. Why? Double BufferingWithout care, animated displays will flicker Due to user seeing sequence of render actions - background cleared, then items drawn back to front Instead, create offscreen buffer (or "back buffer"). Render onto that, and then copy entire back buffer to screen at once. This eliminates flicker, and can be faster for some display hardware - but uses more memory. TextA special kind of graphics for both performance and human reasons. Fonts can be bitmaps or curves (or bitmaps generated from curves) Typically allocate them up front, and then use them on demand. Java & C# have a Font class Java g.setFont(font); g.setColor(color); g.drawString(string, x, y); C# g.drawString(string, font, brush, point); Strings get drawn with dimensions (FontMetrics class, g.getFontMetrics() - Java), (Graphics.MeasureString - C#) How big is a point? Fonts get measured with Ascent, Descent, Height, Leading, Width, Kerning Higher quality text can be drawn with antialiasing or more recently, sub-pixel antialiasing (e.g., Microsoft Cleartype) ColorTrue color (16, 24, 32 bit) vs. indexed color (8 bit) Not reflected in the API, rather the API must support the color representation of the hardware. RGB, HSV, CMYK true-color models ClippingCrucial for a window manager Necessary for application efficiency Necessary for high-end graphics Regions - analytical descriptions of shape. Include algebra for adding/subtracting, manipulating shape. Java - Area class (made of Shapes) Area area = new Area(new Rectangle2D.Double(10, 10, 50, 50)); area.intersect(new Area(new Ellipse2D.Double(30, 30, 50, 50)); g.setClip(area); // or g.setPaint(paint); g.fill(area); C# - Region class Region region = new Region(new Rectangle(10, 10, 50, 50)); GraphicsPath path = new GraphicsPath(); path.AddEllipse(30, 30, 50, 50); g.Clip = region; // or g.FillRegion(brush, region); EfficiencyRegion management (partial redraws) High-level descriptions for networked displays Display lists for complex objects that are redrawn many times Space-time trade-offs. Ex: pre-allocate thumb image and store it, or render it each time? DemoTry jdk1.3\demo\jfc\Java2D\Java2Demo.jar |