Создание paint на java

Обновлено: 07.07.2024

Interface Paint

  • All Superinterfaces: Transparency All Known Implementing Classes: Color, ColorUIResource, GradientPaint, LinearGradientPaint, MultipleGradientPaint, RadialGradientPaint, SystemColor, TexturePaint

This Paint interface defines how color patterns can be generated for Graphics2D operations. A class implementing the Paint interface is added to the Graphics2D context in order to define the color pattern used by the draw and fill methods.

Instances of classes implementing Paint must be read-only because the Graphics2D does not clone these objects when they are set as an attribute with the setPaint method or when the Graphics2D object is itself cloned.

Field Summary

Fields inherited from interface java.awt.Transparency

Method Summary

All Methods Instance Methods Abstract Methods
Modifier and Type Method and Description
PaintContext createContext (ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints)

Creates and returns a PaintContext used to generate the color pattern.

Methods inherited from interface java.awt.Transparency

Method Detail

createContext

Creates and returns a PaintContext used to generate the color pattern. The arguments to this method convey additional information about the rendering operation that may be used or ignored on various implementations of the Paint interface. A caller must pass non- null values for all of the arguments except for the ColorModel argument which may be null to indicate that no specific ColorModel type is preferred. Implementations of the Paint interface are allowed to use or ignore any of the arguments as makes sense for their function, and are not constrained to use the specified ColorModel for the returned PaintContext , even if it is not null . Implementations are allowed to throw NullPointerException for any null argument other than the ColorModel argument, but are not required to do so.

  • Summary:
  • Nested |
  • Field |
  • Constr |
  • Detail:
  • Field |
  • Constr |

Давайте сделаем простую версию Paint - пусть будет можно рисовать:

сначала кружки в местах где накликали мышкой

а затем и линии (там где провели курсором при зажатой кнопке)

1) Создаем окно

Раньше мы действовали так:

создавали свой class MyPanel extends JPanel

затем в main-функции создавали объект окно: JFrame frame = new JFrame;

и добавляли объект нашей панели в окно: frame.add(new MyPanel());

Но почему бы не создать класс который сам по себе является наследником не панели ( JPanel ), а сам по себе является окном?

Давайте так и сделаем (это будет важно для двойной буферизации в самом конце).

Создаем класс MainFrame , который является окном, а значит должен быть отнаследован от JFrame (т.е. class MainFrame extends JFrame < ).

В конструкторе MainFrame нам достаточно указать какой-нибудь разумный размер окна, сказать что мы хотим завершать исполнение программы по нажатию на крестик и сделать окно видимым (все это аналогично тому, что мы делали раньше - только теперь мы вызываем эти методы сами у себя через this.названиеМетода(); ).

Наконец, добавим точку входа ( main -функцию), и наше окошко уже появляется при запуске:

2) Рисуем примитивы

Чтобы что-нибудь нарисовать - надо переопределить метод определяющий то, как JFrame отрисовывается - это метод void paint(Graphics g) (в отличие от того что было при наследовании от панели, ведь там был метод paintComponent ):

Теперь внутри этого метода обращаясь к Graphics g можно рисовать различные примитивы как мы уже привыкли.

Заметьте, что теперь фон окна иногда не чисто серого цвета, а как бы прозрачный - т.е. показывает то, что было на его месте до того как он отрисовался. Это вызвано тем, что раньше JFrame при отрисовке исполнял свой старый метод paint(. ) , который в частности заливал серым цветом все окно, теперь же мы переопределили этот метод, а следовательно код очистки окошка перестал исполняться. Но есть возможность не целиком заменить старый метод, а расширить его - для этого достаточно в начале реализации paint(. ) добавить вызов super.paint(g) . Здесь super это почти как this - только родительская часть текущего объекта, поэтому таким образом можно вызвать родительскую (т.е. JFrame ) реализацию paint .

Чтобы изменить цвет - перед вызовом очередного draw. достаточно вызвать g.setColor(new Color(255, 0, 0)); .

Сложнота: Чтобы изменить толщину штрихов - нужно преобразовать Graphics к Graphics2D и затем вызвать метод setStroke() :

3) Обрабатываем клики мышки

Напоминание: чтобы обрабатывать нажатия мышки надо заявить себя “слушателем мышки”, реализовать методы которые обрабатывают события и зарегистрировать себя как “официального слушателя мышки в этом окне”:

  • Добавить implements MouseListener в объявлении MainFrame , чтобы получилось public class MainFrame extends JFrame implements MouseListener
  • Кликнуть в подсвеченном красным implements, затем нажать Alt+Enter и выбрать Implement methods
  • Добавить в конструктор MainFrame вызов addMouseListener(this); (что зарегистрирует нас как обработчика событий мышки)

Теперь можно например в mouseClicked обрабатывать клики мышки в окне. Чтобы получить координаты мышки надо у аргумента функции обработки события ( void mouseClicked(MouseEvent e) ) вызвать метод getX() и getY() , т.е. e.getX() и e.getY() .

4) Рисуем овалы в местах кликов мышкой

Давайте заведем списки координат где кликнула мышь. Объявим мы их как поля MainFrame :

Теперь в методе mouseClicked достаточно добавлять (методом add ) координаты очередного клика к этим массивам.

Чтобы отрисовать эти клики - надо в методе paint (т.е. в момент очередной отрисовки окна) перебрать все сохраненные на данный момент координаты кликов и отрисовать их, например вот так:

Если теперь запустить и протестировать - мышкой рисовать овалы не получается. Почему? Под отладчиком оказывается что метод mouseClicked отрабатывает нажатия, но после этого paint банально не вызывается.

Дело в том, что отрисовка окна происходит не постоянно, а лишь тогда когда что-то изменилось, например окно изменило размер, или было развернуто на весь экран, или само приложение поняло, что отображаемое в окошке изменилось - а это как раз наш случай, ведь мы приняли решение добавить к отрисовке новый эллипс. Чтобы оповестить окно о том, что ему пора перерисоваться - надо вызвать метод repaint() , что спровоцирует вызов метода paint (т.е. надо сообщить о том, что окно требуется перерисовать. А делать это надо в тот момент, когда мы это поняли - т.е. в методе обработки клика мышью).

Раньше мы этот метод repaint вызывали из вечного цикла while(true) , т.е. избыточно нагружали наш компьютер, но в данном случае перерисовка требуется только когда пользователь кликает.

5) Обрабатываем движения мышью

Это опять про “великих обработчиков”. Все то же самое что и про обработку кликов мышью в пункте 3, разве что реализовывать надо интерфейс MouseMotionListener . В результате получается что-то подобное:

6) Контуры движений мыши

Подумайте, как теперь можно сохранять перечень отрезков отображающих траекторию движения мышки с нажатой кнопкой? Т.е. каждый отрезок - это отрезок между двумя точками, где первая точка - предыдущее положение мыши, а вторая точка - следующее. При этом линия должна выводиться только при зажатой кнопке мыши, если кнопка отпущена - линия завершена, если левая кнопка мыши нажата вновь - новая линия начинается.

7) Двойная буферизация

Когда элементов отрисовки окажется достаточно много - станет заметно мерцание внутри окна. Это вызвано тем, что отрисовка происходит по-элементно, и каждое промежуточное состояние пользователь видит. В результате элементы которые отрисовываются в конце - пользователь наблюдает крайне малое время.

1) Указать число буферов для стратегии буферизации - createBufferStrategy(2); (например вызвав в конце конструирования окна)

2) В методе paint в самом начале сделать вызов super.paint(g)

3) В самом начале метода paint написать следующий код:

4) В самом конце метода paint написать следующий код:

8) Дополнительные идеи

Сделайте так чтобы когда пользователь рисовал очередную загогулину - она выбирала себе случайный цвет и случайную толщину.

Еще вы можете добавить сохранение нарисованного холста в текстовый файл, или даже сделать вашу рисовалку многопользовательской! Для этого изучите материалы в следующей статье.

9) Частые проблемы

Ситуация: Окошко маленькое

Либо вы указали маленький размер окна в пикселях (нормальный размер - это например 640x480), либо что-то упустили в условии.

Ситуация: Окно прозрачное/за ним мусор/остается мусор с предыдущих отрисовок

Вы упустили в условии вызов родительской реализации paint() .

Ситуация: Окно мерцает

Это из-за отсутствия двойной буферизации. Меня устраивает если окно мигает в процессе рисования мышью или в процессе изменения размера окна, но не в состоянии “окно не двигается и изображение статично”. Это можно пофиксить - 7 пункт условия.

So basically I have some code I was working on a couple of days ago that is kind of like Paint, which allows you to essentially draw on the screen using the mouse. I kind of discovered this property by accident, and I realized that it is really inefficient and i'm wondering if there is a more practical way to do this. There isn't really any reason to give all of my code, but here are the important parts

The code block above sets up the jframe (the window)

The code above pretty much just sets the MOUSE_X and MOUSE_Y variables and the repaint(); method

This is the thing I came across accidentally. What I was trying to do when I found this out was basically make a square follow your cursor whenever you moved your mouse. But I forgot to type the code part paintComponent(g); , which turns this program into the thing that I originally intended. The bottom parts of this are essentially how I would clear the board. I'm 100% sure that this isn't the proper way to clear/reset a frame like this, but I couldn't find another way. If anyone has any tips or better methods to use to do this properly I would be very appreciative. Thanks! :D


235 1 1 gold badge 2 2 silver badges 10 10 bronze badges Nope, you should be calling super.paintComponent before painting the current state of the component. Otherwise you could end up with all sorts of mess appearing on your component, like other ghost images of your component, for example To "rest" the view, rest the values which determine how or what is painted before calling repaint

3 Answers 3

You're current approach is basically breaking the requirements of the paint chain, by not calling super.paintComponent . The paintComponent method does a set of operations, which you are not taking over and which could result in some very weird paint artifacts which are difficult to replicate consistently.

Graphics is a shared resource, so the Graphics context which was used to paint some other control will be the same which is used to paint your component, unless you are "cleaning" the context before hand, what was previously painted to the context will remain (which is why you code currently "seems" to work).

Instead, you should use a MouseListener to define a anchor point, which represents the point at which the mouse was pressed and then use the MouseMotionListener to define the extent of the selection area, for example.

Just to highlight the issue you will face if you continue to violate the requirements of the paintComponent method, this is what happens when I don't call super.paintComponent

I simply added two JButton 's to the JFrame (so not even directly to the panel). paintComponent does a series of important jobs, which you neglected to perform, which is going to cause more problems and issues.

A free form line is actually a illusion, it's a series of (small) lines drawn between a series of points, the reason for this is because the MouseListener won't report every mouse position it moves across, depending on the speed the mouse is moved, you might get lots of call backs or a few.

So, instead of drawing to just draw the points, we store the points in a List and draw lines between them, for example.

I'm having a big problem with this: i need to build a program which consist in building a java swing interface that contains 5 squares, and one button. The button function is to draw a circle inside the squares. I have this code, but i dont know how to continue.

The Frame class:

I have this problems and questions:

  • When i hit for the first time the "Next" button, the circle appears and dissapears instantly in the first square. How can i do to let the circle be visible for the first time?
  • When i hit for the second time the button, it appears the circle in the second square. What i don't know and i wanted to ask is this: how can i make to disappear the circle in the first square and only let visible the circle in the second square? What i want to do is the same for the case when the circle is in the third square, i want to make disappear the circle in the second square.
  • If i want to make the circle appear at the beggining of the program, but i want a blue circle, and then the 2°, 3°, 4° and 5° position i want a red circle inside them, how can i do it? (Remember that the circles will appear when i clic the "Next" button.

Thank you very much everyone!

97 1 1 gold badge 2 2 silver badges 12 12 bronze badges

2 Answers 2

Don't use getGraphics() to do painting. That painting is only temporary and will be lost when ever the components repaints itself.

All painting MUST be done in the paintComponent() method. This means you need to set a property in your class that will tell the paintComponent() what to paint.

One way do to this is to keep a List of objects to paint. Then in the paintComponent() method you iterate through the List and paint the objects.

So you might create the List with code like:

The Shape interface allows you to represent geometric shapes, like circles, rectangles, polygons etc. Then when you want to paint the first circle you add a circle Shape to the List with code like:

Finally in the paintComponent() method, after you paint your rectangles, you add code to paint the Shapes in the List.

So whenever you click the next button you clear the List and add a new Shape to paint. Or if you don't clear the List then you can add more circles and they will all be painted.

313k 19 19 gold badges 154 154 silver badges 277 277 bronze badges

THere are some thingies, that you not doing in the right sense.

  1. Firstly, you setting the visible property of the JFrame to visible much before you actually added all components to it, and make it realize its size.
  2. Secondly, you using an Absolute Layout , which one should avoid in most of the situations.
  3. Thirdly, you creating a Graphics object explicitly, which one should avoid and instead use the one provided by Java's paintComponent ( . ) method by default

Have a look at this example:

EDIT regarding comment:

All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance of javax.swing.JFrame . Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT), more info on Concurrency in Swing. This will prevent potential race conditions that could lead to deadlock.

DrawingBoard also overrides getPreferredSize, which returns the desired width and height of the panel (in this case 400 is the width , 300 is the height.) Because of this, the DrawingCircleExample class no longer needs to specify the size of the frame in pixels. It simply adds the panel to the frame and then invokes pack.

The paintComponent method is where all of your custom painting takes place. This method is defined by javax.swing.JComponent and then overridden by your subclasses to provide their custom behavior. Its sole parameter, a java.awt.Graphics object, exposes a number of methods for drawing 2D shapes and obtaining information about the application's graphics environment. In most cases the object that is actually received by this method will be an instance of java.awt.Graphics2D (a Graphics subclass), which provides support for sophisticated 2D graphics rendering.

Most of the standard Swing components have their look and feel implemented by separate "UI Delegate" objects. The invocation of super.paintComponent(g) passes the graphics context off to the component's UI delegate, which paints the panel's background.

To keep our custom painting as efficient as possible, we will track the X coordinates ( moveXBy variable in our case ) and repaint only the areas of the screen that have changed. This is a recommended best practice that will keep your application running as efficiently as possible.

Because we are manually setting the clip, our setState method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the oval previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the oval currently is. An important point worth noting is that although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing.

Читайте также: