Page 1 of 1

DPI and image

Posted: Fri Oct 18, 2024 7:31 pm
by mestnyi
on Windows on my laptop with a resolution of 2880X1800 and a scale of 200
the following example shows a small image.
It seems to me that createimage() was supposed to create images at scale. :oops:

Code: Select all

;font = LoadFont( 0, "Aria", DesktopUnscaledX(13) )
;font = GetGadgetFont(#PB_Default)
font = LoadFont( 0, "Aria", (13) )

CreateImage( 0, 136, 136 )
If StartDrawing( ImageOutput( 0 ) )
  DrawingFont( font )
  
  Box( 0, 0, 136, 136, $FFFFFF )
  DrawText( 5, 5, "Drag this image", $000000, $FFFFFF )        
  For i = 45 To 1 Step -1
    Circle( 70, 80, i, Random( $FFFFFF ) )
  Next i        
  
  StopDrawing( )
EndIf  

CompilerIf #PB_Compiler_DPIAware
  text.s = "enable DPIAware"
CompilerElse
  text.s = "disable DPIAware"
CompilerEndIf

If OpenWindow(0, 0, 0, 305, 160, text, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ImageGadget(0,  10, 10, 136, 136, ImageID(0))                     ; imagegadget standard
  ImageGadget(1, 156, 10, 136, 136, ImageID(0), #PB_Image_Border)   ; imagegadget with border
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Re: DPI and image

Posted: Fri Oct 18, 2024 7:36 pm
by Fred
For all pixel based gadgets (image, canvas) you need to scale yourself with DesktopScaledX/Y()

Re: DPI and image

Posted: Fri Oct 18, 2024 7:53 pm
by mestnyi
Do you mean this?
But there are big problems here. You need to make a lot of changes every time, isn't it better to change the functions once so that the result is the same with and without dpi resolution?

Code: Select all

;font = LoadFont( 0, "Aria", DesktopUnscaledX(13) )
;font = GetGadgetFont(#PB_Default)
font = LoadFont( 0, "Aria", (13) )

CreateImage( 0, DesktopScaledX(136), DesktopScaledY(136) )
If StartDrawing( ImageOutput( 0 ) )
  DrawingFont( font )
  
  Box( 0, 0, DesktopScaledX(136), DesktopScaledY(136), $FFFFFF )
  DrawText( 5, 5, "Drag this image", $000000, $FFFFFF )        
  For i = 45 To 1 Step -1
    Circle( DesktopScaledX(70), DesktopScaledY(80), DesktopScaledX(i), Random( $FFFFFF ) )
  Next i        
  
  StopDrawing( )
EndIf  

CompilerIf #PB_Compiler_DPIAware
  text.s = "enable DPIAware"
CompilerElse
  text.s = "disable DPIAware"
CompilerEndIf

If OpenWindow(0, 0, 0, 305, 160, text, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ImageGadget(0,  10, 10, 136, 136, ImageID(0))                     ; imagegadget standard
  ImageGadget(1, 156, 10, 136, 136, ImageID(0), #PB_Image_Border)   ; imagegadget with border
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Re: DPI and image

Posted: Fri Oct 18, 2024 8:46 pm
by Fred
No, it's not possible as it would work only for simple drawing. If you don't care about image clarity, you can just call ResizeImage() after your drawing (but it kind of defeat the high DPI stuff). Another way is to use VectorDrawing() which allow to use a scale factor.

Re: DPI and image

Posted: Fri Oct 18, 2024 10:02 pm
by idle
might be easier to use a set of macros to wrap the drawing functions and use an additional flag, if you specifically want drawing operations scaled.

Code: Select all

#ScaleAll =1

Procedure _CreateImage(image,width,height,depth=32,color=0) 
  CompilerIf (#PB_Compiler_DPIAware And #scaleall)
    CreateImage(image,DesktopScaledX(width),DesktopScaledY(height),depth,color)
  CompilerElse
    CreateImage(image,width,height,depth,color)
  CompilerEndIf
EndProcedure   

Macro CreateImage(image,width,height,depth=32,color=0) 
  _CreateImage(image,width,height,depth,color) 
EndMacro  

Procedure _Circle(x,y,radius,color)
  CompilerIf (#PB_Compiler_DPIAware And #scaleall)
    Circle(DesktopScaledX(x),DesktopScaledY(y),DesktopScaledX(radius),color) 
  CompilerElse 
    Circle(x,y,radius,color) 
  CompilerEndIf
EndProcedure 

Macro Circle(x,y,radius,color) 
  _Circle(x,y,radius,color)
EndMacro   

Procedure _Box(x,y,w,h,color) 
   CompilerIf (#PB_Compiler_DPIAware And #scaleall)
     Box(DesktopScaledX(x),DesktopScaledY(y),DesktopScaledX(w),DesktopScaledY(h),color) 
   CompilerElse 
     Box(x,y,w,h,color) 
   CompilerEndIf 
 EndProcedure 
 
 Macro Box(x,y,w,h,color) 
   _box(x,y,w,h,color) 
 EndMacro   
    

font = LoadFont( 0, "Aria", (13) )

CreateImage(0,136,136)
If StartDrawing( ImageOutput( 0 ) )
  DrawingFont( font )
  
  Box( 0, 0, 136, 136, $FFFFFF )
  DrawText( 5, 5, "Drag this image", $000000, $FFFFFF )        
  For i = 45 To 1 Step -1
    Circle( 70, 80, i, Random( $FFFFFF ) )
  Next i        
  
  StopDrawing( )
EndIf  

CompilerIf #PB_Compiler_DPIAware
  text.s = "enable DPIAware"
CompilerElse
  text.s = "disable DPIAware"
CompilerEndIf

If OpenWindow(0, 0, 0, 305, 160, text, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ImageGadget(0,  10, 10, 136, 136, ImageID(0))                     ; imagegadget standard
  ImageGadget(1, 156, 10, 136, 136, ImageID(0), #PB_Image_Border)   ; imagegadget with border
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf


Re: DPI and image

Posted: Sat Oct 26, 2024 3:08 pm
by mestnyi
You can see what happens if you uncomment on line 4. The text looks the same with dpi enabled and without.

Code: Select all

CreateImage(0,136,136)
If StartDrawing( ImageOutput( 0 ) )
  ; comment/uncomment to see
  ; DrawingFont(GetGadgetFont( #PB_Default ))
  
  Box( 10, 10, 136-20, 136-20, $FFFFFF )
  DrawText( 5, 5, "Drag this image", $000000, $FFFFFF )        
  
  StopDrawing( )
EndIf  

If OpenWindow(0, 0, 0, 160, 160, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ImageGadget(1, 10, 10, 136, 136, ImageID(0), #PB_Image_Border) 
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
And I don't understand what would be so terrible if all 2D drawing functions took DPI into account. As idle suggested, if I wrap all the 2D drawing functions under a macro, I didn't see any problems.

Re: DPI and image

Posted: Sat Oct 26, 2024 3:43 pm
by mestnyi
All the examples given in the help work with these macros as expected.
The only thing is, if we comment out the line "Point()", we will get an error, but I think this is an error of the function itself

Code: Select all

CompilerIf #PB_Compiler_DPIAware And #PB_Compiler_OS = #PB_OS_Windows
  Macro PB(Function)
    Function
  EndMacro
  
  Global DPISCALEDX.d = (GetDeviceCaps_(GetDC_(0),#LOGPIXELSX) / 96)
  Global DPISCALEDY.d = (GetDeviceCaps_(GetDC_(0),#LOGPIXELSY) / 96)
  
  Macro DPIResolutionX( )
    DPISCALEDX;(GetDeviceCaps_(GetDC_(0),#LOGPIXELSX) / 96)
  EndMacro
  Macro DPIResolutionY( )
    DPISCALEDY;(GetDeviceCaps_(GetDC_(0),#LOGPIXELSY) / 96)
  EndMacro
  Macro DPIScaledX( _x_ )
    ((_x_) * DPIResolutionX( )) ; DesktopScaledX(_x_) ; 
  EndMacro
  Macro DPIScaledY( _y_ )
    ((_y_) * DPIResolutionY( )) ; DesktopScaledY(_y_) ; 
  EndMacro
  Macro DPIUnscaledX( _x_ )
    ((_x_) / DPIResolutionX( )) ; DesktopUnscaledX(_x_) ; 
  EndMacro
  Macro DPIUnscaledY( _y_ )
    ((_y_) / DPIResolutionY( )) ; DesktopUnscaledY(_y_) ; 
  EndMacro
  
  Macro _dq_
    "
  EndMacro
  
  Macro ImageWidth( _image_  )
    DPIUnscaledX(PB(ImageWidth)( _image_ ))
  EndMacro
  
  Macro ImageHeight( _image_  )
    DPIUnscaledY(PB(ImageHeight)( _image_ ))
  EndMacro
  
  Macro CreateImage( _image_, _width_, _height_, _depth_= 24, _backcolor_= #PB_Image_Transparent   )
    PB(CreateImage)( _image_, DPIScaledX(_width_), DPIScaledY(_height_), _depth_, _backcolor_ )
  EndMacro
  
  Macro DrawImage(_imageID_, _x_, _y_, _width_= #PB_Ignore, _height_= #PB_Ignore)
    CompilerIf _dq_#_width_#_dq_ = "" And _dq_#_height_#_dq_ = ""
      PB(DrawImage)(_imageID_, DPIScaledX(_x_), DPIScaledY(_y_))
    CompilerElseIf  _dq_#_height_#_dq_ = ""
      PB(DrawImage)(_imageID_, DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_))
    CompilerElse 
      PB(DrawImage)(_imageID_, DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_))
    CompilerEndIf
  EndMacro
  
  Macro DrawAlphaImage(_imageID_, _x_, _y_, _alpha_ = 255)
    PB(DrawAlphaImage)(_imageID_, DPIScaledX(_x_), DPIScaledY(_y_), _alpha_)
  EndMacro
  
  Macro TextWidth( _Text_  )
    DPIUnscaledX(PB(TextWidth)( _Text_ ))
  EndMacro
  
  Macro TextHeight( _Text_  )
    DPIUnscaledY(PB(TextHeight)( _Text_ ))
  EndMacro
  
  Macro DrawText(_x_, _y_, _text_, _frontcolor_= , _backcolor_= )
    CompilerIf _dq_#_frontcolor_#_dq_ = "" And _dq_#_backcolor_#_dq_ = ""
      PB(DrawText)(DPIScaledX(_x_), DPIScaledY(_y_), _text_)
    CompilerElseIf _dq_#_backcolor_#_dq_ = ""
      PB(DrawText)(DPIScaledX(_x_), DPIScaledY(_y_), _text_, _frontcolor_)
    CompilerElse
      PB(DrawText)(DPIScaledX(_x_), DPIScaledY(_y_), _text_, _frontcolor_, _backcolor_)
    CompilerEndIf
  EndMacro 
  
  Macro DrawRotatedText(_x_, _y_, _text_, _angle_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(DrawRotatedText)(DPIScaledX(_x_), DPIScaledY(_y_), _text_, _angle_)
    CompilerElse
      PB(DrawRotatedText)(DPIScaledX(_x_), DPIScaledY(_y_), _text_, _angle_, _color_)
    CompilerEndIf
  EndMacro 
  
  Macro ClipOutput(_x_, _y_, _width_, _height_)
    PB(ClipOutput)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_))
  EndMacro
  
  Macro Circle( _x_, _y_, _radius_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(Circle)( DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radius_) )
    CompilerElse
      PB(Circle)( DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radius_), _color_ )
    CompilerEndIf
  EndMacro
  
  Macro CircularGradient( _x_, _y_, _radius_)
    PB(CircularGradient)( DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radius_) )
  EndMacro
  
  Macro ConicalGradient( _x_, _y_, _angle_)
    PB(ConicalGradient)( DPIScaledX(_x_), DPIScaledY(_y_), _angle_ )
  EndMacro
  
  Macro Line(_x_, _y_, _width_, _height_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(Line)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_))
    CompilerElse
      PB(Line)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_), _color_)
    CompilerEndIf
  EndMacro
  
  Macro LineXY(_x1_, _y1_, _x2_, _y2_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(LineXY)(DPIScaledX(_x1_), DPIScaledY(_y1_), DPIScaledX(_x2_), DPIScaledY(_y2_))
    CompilerElse
      PB(LineXY)(DPIScaledX(_x1_), DPIScaledY(_y1_), DPIScaledX(_x2_), DPIScaledY(_y2_), _color_)
    CompilerEndIf
  EndMacro
  
  Macro LinearGradient(_x1_, _y1_, _x2_, _y2_)
    PB(LinearGradient)(DPIScaledX(_x1_), DPIScaledY(_y1_), DPIScaledX(_x2_), DPIScaledY(_y2_))
  EndMacro
  
  Macro Box(_x_, _y_, _width_, _height_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(Box)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_))
    CompilerElse
      PB(Box)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_), _color_)
    CompilerEndIf
  EndMacro
  
  Macro BoxedGradient(_x_, _y_, _width_, _height_)
    PB(BoxedGradient)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_))
  EndMacro
  
  Macro RoundBox(_x_, _y_, _width_, _height_, _roundx_,roundy_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(RoundBox)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_), DPIScaledX(_roundx_), DPIScaledY(roundy_))
    CompilerElse
      PB(RoundBox)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_width_), DPIScaledY(_height_), DPIScaledX(_roundx_), DPIScaledY(roundy_), _color_)
    CompilerEndIf
  EndMacro
  
  Macro Ellipse(_x_, _y_, _radiusx_,radiusy_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(Ellipse)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radiusx_), DPIScaledY(radiusy_))
    CompilerElse
      PB(Ellipse)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radiusx_), DPIScaledY(radiusy_), _color_)
    CompilerEndIf
  EndMacro
  
  Macro EllipticalGradient(_x_, _y_, _radiusx_, radiusy_)
    PB(EllipticalGradient)(DPIScaledX(_x_), DPIScaledY(_y_), DPIScaledX(_radiusx_), DPIScaledY(radiusy_))
  EndMacro
  
  Macro FillArea(_x_, _y_, _outlinecolor_, _color_= )
    CompilerIf _dq_#_color_#_dq_ = "" 
      PB(FillArea)(DPIScaledX(_x_), DPIScaledY(_y_), _outlinecolor_)
    CompilerElse
      PB(FillArea)(DPIScaledX(_x_), DPIScaledY(_y_), _outlinecolor_, _color_)
    CompilerEndIf
  EndMacro 
  
  Macro OutputWidth( )
    DPIUnscaledX(PB(OutputWidth)( ))
  EndMacro
  
  Macro OutputHeight( )
    DPIUnscaledY(PB(OutputHeight)( ))
  EndMacro
  
  Macro GetOriginX( )
    DPIUnscaledX(PB(GetOriginX)( ))
  EndMacro
  
  Macro GetOriginY( )
    DPIUnscaledY(PB(GetOriginY)( ))
  EndMacro
  
  Macro SetOrigin(_x_, _y_)
    PB(SetOrigin)(DPIScaledX(_x_), DPIScaledY(_y_))
  EndMacro 
  
  Macro Plot(_x_, _y_, _color_= )
    Box(_x_, _y_, 1,1, _color_)
    ; Line(_x_, _y_, 1,1, _color_)
    
    ;        CompilerIf _dq_#_color_#_dq_ = "" 
    ;          PB(Plot)(DPIScaledX(_x_), DPIScaledY(_y_))
    ;        CompilerElse
    ;          PB(Plot)(DPIScaledX(_x_), DPIScaledY(_y_), _color_)
    ;        CompilerEndIf
  EndMacro 
  
  Macro Point(_x_, _y_)
    PB(Point)(DPIScaledX(_x_), DPIScaledY(_y_))
  EndMacro 
CompilerEndIf 



If OpenWindow(0, 0, 0, 200, 200, "2DDrawing Example DPI", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If CreateImage(0, 200, 200, 24, $FFFFFF) And StartDrawing(ImageOutput(0))
    
    ; Draw the same figure at different locations by moving the drawing origin
    For x = 0 To 120 Step 40
      For y = 0 To 120 Step 60
        
        SetOrigin(x, y)
        ; Debug ""+ x +" "+ GetOriginX() +" "+ y +" "+ GetOriginY()
        
        Box(0, 0, 30, 30, $FF0000)
        Circle(15, 15, 10, $00FF00)
        
      Next y
    Next x   
    
    StopDrawing() 
    ImageGadget(0, 0, 0, 200, 200, ImageID(0))      
  EndIf
  
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf
Fred, if you're not going to fix this, please give me the default values for these functions. :oops:

Re: DPI and image

Posted: Sun Oct 27, 2024 10:56 am
by mestnyi
That's what I mean.
"Macro Box(_x_, _y_, _width_, _height_, _color_=?)"
what should be the value color?
Please, Fred, I really need this. :oops:

Code: Select all

CompilerIf #PB_Compiler_DPIAware 
  Macro PB(Function)
    Function
  EndMacro
  
  Macro CreateImage( _image_, _width_, _height_, _depth_= 24, _backcolor_= -1  )
    PB(CreateImage)( _image_, DesktopScaledX(_width_), DesktopScaledY(_height_), _depth_, _backcolor_ )
  EndMacro
  
  Macro Box(_x_, _y_, _width_, _height_, _color_=-1)
    PB(Box)(DesktopScaledX(_x_), DesktopScaledY(_y_), DesktopScaledX(_width_), DesktopScaledY(_height_), _color_)
  EndMacro
CompilerEndIf 


If OpenWindow(0, 0, 0, 200, 200, "2DDrawing Example DPI", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If CreateImage(0, 200, 200, 24, $FFFFFF) And StartDrawing(ImageOutput(0))
    x=40
    y=40
    
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(x, y, 30, 30, $FF0000)
    
    FrontColor($0000FF)
    
    DrawingMode(#PB_2DDrawing_Default)
    Box(x+1, y+1, 28, 28)
    
    StopDrawing() 
    ImageGadget(0, 0, 0, 200, 200, ImageID(0))      
  EndIf
  
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf

Re: DPI and image

Posted: Sun Oct 27, 2024 10:59 am
by Fred
It won't be fixed as it's not an issue. You need to adapt you graphics to the dpi and the 2ddrawing lib is a pixel based library which is has nothing to do with dpi. As said before, you can use the vector lib if you need automatic scaling.

About the default color, you can just test with the current function, but it should use the current frontcolor IIRC

Re: DPI and image

Posted: Sun Oct 27, 2024 12:53 pm
by mestnyi
Fred wrote: Sun Oct 27, 2024 10:59 am About the default color, you can just test with the current function, but it should use the current frontcolor IIRC
Can you use my last example to show what you mean?

Re: DPI and image

Posted: Sun Oct 27, 2024 1:45 pm
by mk-soft
Small trick ...

Code: Select all

CompilerIf 1;#PB_Compiler_DPIAware 
  Macro _dq_
    "
  EndMacro
  
  Macro PB(Function)
    Function
  EndMacro
  
  Macro CreateImage( _image_, _width_, _height_, _depth_= 24, _backcolor_=-1)
    PB(CreateImage)( _image_, DesktopScaledX(_width_), DesktopScaledY(_height_), _depth_, _backcolor_)
  EndMacro
  
  Macro Box(_x_, _y_, _width_, _height_, _color_=)
    CompilerIf _dq_#_color_#_dq_ <> "" 
      PB(Box)(DesktopScaledX(_x_), DesktopScaledY(_y_), DesktopScaledX(_width_), DesktopScaledY(_height_), _color_)
    CompilerElse
      PB(Box)(DesktopScaledX(_x_), DesktopScaledY(_y_), DesktopScaledX(_width_), DesktopScaledY(_height_))
    CompilerEndIf
  EndMacro
CompilerEndIf 


If OpenWindow(0, 0, 0, 200, 200, "2DDrawing Example DPI", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If CreateImage(0, 200, 200, 24, $FFFFFF) And StartDrawing(ImageOutput(0))
    x=40
    y=40
    
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(x, y, 30, 30, $FF0000)
    ;Box(x+5, y+5, 20, 20)
    
    FrontColor($0000FF)
    
    DrawingMode(#PB_2DDrawing_Default)
    Box(x+1, y+1, 28, 28)
    
    StopDrawing() 
    ImageGadget(0, 0, 0, 200, 200, ImageID(0))      
  EndIf
  
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf

Re: DPI and image

Posted: Mon Oct 28, 2024 3:55 am
by mestnyi
mk-soft wrote: Sun Oct 27, 2024 1:45 pm Small trick ...
Thank you for that, it's a great solution. Only it won't work with those functions that are supposed to return something. :oops: It happened by accident.

Re: DPI and image

Posted: Mon Oct 28, 2024 4:59 pm
by mk-soft
mestnyi wrote: Mon Oct 28, 2024 3:55 am
mk-soft wrote: Sun Oct 27, 2024 1:45 pm Small trick ...
Спасибо вам за это, это отличное решение. Только оно не будет работать с теми функциями, которые должны что-то возвращать.
Please in English ...

Here in German :mrgreen:
Danke dafür, es ist eine tolle Entscheidung. Nur wird es nicht mit den Funktionen funktionieren, die etwas zurückgeben sollten.

Re: DPI and image

Posted: Wed Oct 30, 2024 7:50 pm
by mestnyi
Fred wrote: Sun Oct 27, 2024 10:59 am It won't be fixed as it's not an issue. You need to adapt you graphics to the dpi and the 2ddrawing lib is a pixel based library which is has nothing to do with dpi. As said before, you can use the vector lib if you need automatic scaling.
I've done a lot of tests, although at first glance it seemed to me that it would be better this way. You were right. what did not agree with me, now I agree to leave it as it is. :)