Need a palette expert here

A

active

Below is a small but complete program that appears to show you
can't retrive a Palette from the clipboard.

This is true whether the palette is placed on the clipboard by Photoshop or
Photoshop or by the below program

It lets you place a palette from a file or from a program on the clipboard
and allows you to retrieve one from the clipboard. But you'll find the
retrieved one is always "Nothing"

I'd appreciate comments



Imports System.Drawing.Imaging

Public Class Form1

Inherits System.Windows.Forms.Form

Private WithEvents button1 As System.Windows.Forms.Button

Private WithEvents button2 As System.Windows.Forms.Button

Private WithEvents button3 As System.Windows.Forms.Button

Private components As System.ComponentModel.IContainer

Private cp As ColorPalette

Friend WithEvents ToolTip1 As System.Windows.Forms.ToolTip

Friend WithEvents TextBox1 As System.Windows.Forms.TextBox

Friend WithEvents TextBox2 As System.Windows.Forms.TextBox

Friend WithEvents Timer1 As System.Windows.Forms.Timer

Public Sub New()

InitializeComponent()

End Sub 'New

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing Then

If Not (components Is Nothing) Then

components.Dispose()

End If

End If

MyBase.Dispose(disposing)

End Sub 'Dispose

#Region "Windows Form Designer generated code"

Private Sub InitializeComponent()

Me.components = New System.ComponentModel.Container

Me.button1 = New System.Windows.Forms.Button

Me.button2 = New System.Windows.Forms.Button

Me.button3 = New System.Windows.Forms.Button

Me.ToolTip1 = New System.Windows.Forms.ToolTip(Me.components)

Me.TextBox1 = New System.Windows.Forms.TextBox

Me.TextBox2 = New System.Windows.Forms.TextBox

Me.Timer1 = New System.Windows.Forms.Timer(Me.components)

Me.SuspendLayout()

'

'button1

'

Me.button1.Location = New System.Drawing.Point(186, 124)

Me.button1.Name = "button1"

Me.button1.Size = New System.Drawing.Size(130, 24)

Me.button1.TabIndex = 2

Me.button1.Text = "Copy File Palette"

Me.ToolTip1.SetToolTip(Me.button1, "Copy Palette from a GIF file to the
clipboard")

'

'button2

'

Me.button2.Location = New System.Drawing.Point(13, 124)

Me.button2.Name = "button2"

Me.button2.Size = New System.Drawing.Size(156, 24)

Me.button2.TabIndex = 2

Me.button2.Text = "Retrieve Clipboard Palette"

Me.ToolTip1.SetToolTip(Me.button2, "Retrieve a palette from the clipboard")

'

'button3

'

Me.button3.Location = New System.Drawing.Point(88, 167)

Me.button3.Name = "button3"

Me.button3.Size = New System.Drawing.Size(130, 24)

Me.button3.TabIndex = 2

Me.button3.Text = "Exit"

'

'TextBox1

'

Me.TextBox1.Location = New System.Drawing.Point(12, 24)

Me.TextBox1.Multiline = True

Me.TextBox1.Name = "TextBox1"

Me.TextBox1.Size = New System.Drawing.Size(304, 47)

Me.TextBox1.TabIndex = 3

Me.TextBox1.Text = "Palette can be placed on clipboard by using the 'Save
Palette"" button or by some " & _

"external program such as Photoshop"

'

'TextBox2

'

Me.TextBox2.Location = New System.Drawing.Point(13, 85)

Me.TextBox2.Name = "TextBox2"

Me.TextBox2.Size = New System.Drawing.Size(302, 20)

Me.TextBox2.TabIndex = 4

'

'Timer1

'

Me.Timer1.Enabled = True

'

'Form1

'

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

Me.ClientSize = New System.Drawing.Size(328, 204)

Me.Controls.Add(Me.TextBox2)

Me.Controls.Add(Me.TextBox1)

Me.Controls.Add(Me.button1)

Me.Controls.Add(Me.button2)

Me.Controls.Add(Me.button3)

Me.Name = "Form1"

Me.Text = "Form1"

Me.ResumeLayout(False)

Me.PerformLayout()

End Sub 'InitializeComponent

#End Region

<STAThread()> Shared Sub Main()

Application.Run(New Form1)

End Sub 'Main

Private Sub button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button1.Click

Dim dlg As New OpenFileDialog

dlg.Filter = "GIF files|*.GIF"

If dlg.ShowDialog() = DialogResult.OK Then

Dim zz As Image = Image.FromFile(dlg.FileName)

Dim DataO As New DataObject

DataO.SetData(DataFormats.Bitmap, False, CType(zz, Bitmap))

DataO.SetData(DataFormats.Palette.ToString, False, zz.Palette)

Clipboard.SetDataObject(DataO, False)

End If

End Sub 'button1_Click

Private Sub button2_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button2.Click

Dim DataO As DataObject = CType(Clipboard.GetDataObject(), DataObject)

If DataO.GetDataPresent(DataFormats.Palette, False) Then

cp = CType(DataO.GetData(DataFormats.Palette), ColorPalette)

If cp Is Nothing Then

MessageBox.Show("Contains palette that is Nothing")

Else

MessageBox.Show(cp.ToString)

End If

Else

MessageBox.Show("No Palette on Clipboard")

End If


End Sub 'button2_Click

Private Sub button3_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button3.Click

Application.Exit()

End Sub 'button3_Click

Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs)
Handles Timer1.Tick

Dim DataO As DataObject = CType(Clipboard.GetDataObject(), DataObject)

If DataO.GetDataPresent(DataFormats.Palette, False) Then

Dim cp As ColorPalette = CType(DataO.GetData(DataFormats.Palette),
ColorPalette)

TextBox2.Text = "There is a Palette on the Clipboard"

Else

TextBox2.Text = "The Clipboard does not contain a Palette"

End If

End Sub

End Class 'Form1
 
M

Michael Phillips, Jr.

The DataFormats.Palette is a private clipboard format.

Perhaps Microsoft intended this format to represent the new ColorPalette
format but they provided a flawed implementation of the DataObject class.

When you query the clipboard using GetDataPresent( DataFormats.Palette,
false) or GetDataPresent( DataFormats.Palette, true), the return value is
true.

If you try to retrieve the data using GetData(DataFormats.Palette), the
return value is null.

The canonical clipboard format is CF_PALETTE which is represented as an GDI
HPALETTE.

I am unaware of any application that uses the private clipboard format
DataFormats.Palette and places the palette on the clipboard as a
ColorPalette object.

The Microsoft implementation of the DataObject class does not convert the
GDI HPALETTE to a ColorPalette object.

You need to do this on your own.

Use P-Invoke to get the HPALETTE using the CF_PALETTE clipboard format and
convert it to a new ColorPalette object using the GDI functions GetObject
and GetPaletteEntries.
 
A

active

The only non-DotNet way that I know of that I can use to put a Palette on
the clipboard is with Photoshop, which is what I use to test.

I do the following and always get HPalette=0

thanks for the info

Public Declare Auto Function OpenClipboard Lib "user32" (ByVal hWnd As
IntPtr) As Integer

Public Declare Auto Function GetClipboardData Lib "user32" (ByVal wFormat As
Integer) As IntPtr

Dim HPalette As IntPtr

Const CF_PALETTE As Integer = 9

If Wnd.OpenClipboard(IntPtr.Zero) <> 0 Then

HPalette = Wnd.GetClipboardData(CF_PALETTE)
 
A

active

I forgot the IsClipboardFormatAvailable lines
The only non-DotNet way that I know of that I can use to put a Palette on
the clipboard is with Photoshop, which is what I use to test.

I do the following and always get HPalette=0

thanks for the info

Public Declare Auto Function OpenClipboard Lib "user32" (ByVal hWnd As
IntPtr) As Integer

Public Declare Auto Function GetClipboardData Lib "user32" (ByVal wFormat
As Integer) As IntPtr
Public Declare Auto Function IsClipboardFormatAvailable Lib "user32" (ByVal
wFormat As Integer) As Boolean
 
M

Michael Phillips, Jr.

Here are the steps:
1) OpenClipboard
2) IsClipboardFormatAvailable(CF_PALETTE)
3) If true, then GetClipboardData(CF_PALETTE)
4) Use OleDuplicateData to duplicate the palette handle as the clipboard
owns the HPALETTE
5) Use GetObject to get the number of palette entries
6) Use GetPaletteEntries to retrieve the palette
7) Use Image property Palette to get the current ColorPalette from the image
8) Use the palette from step 5 to populate the ColorPalette object with the
new palette
9) Assign the Image.Palette property with the new palette
10) CloseClipboard
 
A

active

Thanks a lot for staying with this.
I can't think of anything to do that might help move forward even a little.


Michael Phillips said:
Here are the steps:
1) OpenClipboard
2) IsClipboardFormatAvailable(CF_PALETTE)
3) If true, then GetClipboardData(CF_PALETTE)
....snip

Isn't the above the same as I showed below.
If it is, step 3 always produces zero
I can't seem to get past that

Thanks again the info


....snip.......snip
 
M

Michael Phillips, Jr.

2) IsClipboardFormatAvailable(CF_PALETTE)
3) If true, then GetClipboardData(CF_PALETTE)

GetClipboardData should return a valid handle.

If it returns null, then use GetLastError() to determine why.

Use the free clipboard viewer that comes with all versions of windows. It
will display the palette if there is a CF_PALETTE on the clipboard.
If OpenClipboard(IntPtr.Zero) <> 0

You should use a valid window handle of your application's form window. Do
not use the default, the desktop's window.
 
A

active

Use the free clipboard viewer that comes with all versions of windows. It
will display the palette if there is a CF_PALETTE on the clipboard.

I'm using Windows XP and have never been able to display a palette using the
clipbrd program. It always give the message that it is unable to display it
althought it lists Palette as one of the formats. Does it actually work for
you?
=========
You should use a valid window handle of your application's form window.
Do not use the default, the desktop's window.

I changed to Me.Handle but isn't that what the above did?

hWndNewOwner
[in] Handle to the window to be associated with the open clipboard. If this
parameter is NULL, the open clipboard is associated with the current task.
======

Dim N As Integer = 0
For I As Integer = 0 To cp.Entries.Length - 1
LogPal.palEntry(N) = cp.Entries(I).R
N += 1
LogPal.palEntry(N) = cp.Entries(I).G
N += 1
LogPal.palEntry(N) = cp.Entries(I).B
N += 1
LogPal.palEntry(N) = 0 N += 1
Next
Is the above the correct order to fill LOGPALETTE
bytes in from a color table?

=====================
LogPal.palVersion = &H300S

I have no idea if this is still a good number to use?

==============

<StructLayout(LayoutKind.Sequential, Pack:=1, CharSet:=CharSet.Auto)> _
Public Structure LOGPALETTE
Public palVersion As Short
Public palNumEntries As Short
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=1024)> Public palEntry() As
Byte
End Structure
 
A

active

Michael Phillips said:
Here are the steps:
1) OpenClipboard
2) IsClipboardFormatAvailable(CF_PALETTE)
3) If true, then GetClipboardData(CF_PALETTE)
4) Use OleDuplicateData to duplicate the palette handle as the clipboard
owns the HPALETTE
5) Use GetObject to get the number of palette entries
6) Use GetPaletteEntries to retrieve the palette
7) Use Image property Palette to get the current ColorPalette from the
image
8) Use the palette from step 5 to populate the ColorPalette object with
the new palette
9) Assign the Image.Palette property with the new palette
10) CloseClipboard

I'm still chipping away at this but wanted to say how much I appreciate the
precise instructions.

Thanks
 
M

Michael Phillips, Jr.

I'm using Windows XP and have never been able to display a palette using
the clipbrd program. It always give the message that it is unable to
display it althought it lists Palette as one of the formats. Does it
actually work for you?

Yes. If you cannot display a palette and it is available, check to see if
your display driver supports palettes. Use GetDeviceCaps.
If OpenClipboard(IntPtr.Zero) <> 0
I changed to Me.Handle but isn't that what the above did?

No. If you use null, then your application can't be a clipboard owner and
therefore cannot place objects on the clipboard.
SetClipboardData will always fail!
Dim N As Integer = 0
For I As Integer = 0 To cp.Entries.Length - 1
LogPal.palEntry(N) = cp.Entries(I).R
N += 1
LogPal.palEntry(N) = cp.Entries(I).G
N += 1
LogPal.palEntry(N) = cp.Entries(I).B
N += 1
LogPal.palEntry(N) = 0 N += 1
Next
Is the above the correct order to fill LOGPALETTE
bytes in from a color table?
Yes.

LogPal.palVersion = &H300S
I have no idea if this is still a good number to use?

Yes, it is still a good number.


The easiest way to see the clipboard process is to create a test application
that opens a .gif file and then places and retrieves the palette form the
clipboard.

If you still cannot get an HPALETTE from the clipboard, then post the error
that you get from GetLastError.
 
M

Michael Phillips, Jr.

It may be frustrating but you will learn a great deal by working through a
complete test case yourself!
 
A

active

Michael Phillips said:
Yes. If you cannot display a palette and it is available, check to see if
your display driver supports palettes. Use GetDeviceCaps.
My code said it did not suport palettes (see below) so I put the display
into a 256 color mode and then
my code said it did support palettes, and
clipbrd did display palettes placed there by Photoshop.

What can I conclude?

To obtain the palette entries from the clipboard my display must be in a
mode that uses palettes?
That seems to be true of clipbrd.

I have a nvidia geforce fx5200 display.

If you hadn't alerted me to check the display I don't think I would have
ever thought of that.

Thanks again


============================
hDCSrc = GetWindowDC(Me.Handle)

If (GetDeviceCaps(hDCSrc, RASTERCAPS) And RC_PALETTE) = RC_PALETTE Then

PaletteSize = GetDeviceCaps(hDCSrc, SIZEPALETTE)

MessageBox.Show(Format("Palette size = {0}", PaletteSize))

Else

MessageBox.Show("Display driver does not support palettes")

End If
 
A

active

It appears I can't place a good palette on the clipboard.

The below runs without error and makes

ClipboardFormatAvailable(CF_PALETTE) return true

and clipbrd show Palette as one of the formats.

However clipbrd does not display the palette. And

MyPalHandle = OleDuplicateData(Hpal, CF_PALETTE, 0) returns 0

with the error message:

"Not enough storage to process the command" (I have a gig of memory)



I removed all error checking and comments so as to make the code as easy to
read as possible

I'd appreciate any helpful comments at all



Dim LogPal As LOGPALETTE

Dim Hpal As IntPtr

Dim cp As ColorPalette

Dim dlg As New OpenFileDialog

dlg.Filter = "GIF files|*.GIF"

If dlg.ShowDialog() = DialogResult.OK Then

Dim zz As Bitmap = Image.FromFile(dlg.FileName)

cp = zz.Palette

LogPal.palVersion = &H300S

LogPal.palNumEntries = 256

ReDim LogPal.palEntry(1024)

Dim N As Integer = 0

For I As Integer = 0 To cp.Entries.Length - 1

LogPal.palEntry(N) = cp.Entries(I).R

N += 1

LogPal.palEntry(N) = cp.Entries(I).G

N += 1

LogPal.palEntry(N) = cp.Entries(I).B

N += 1

LogPal.palEntry(N) = 0

N += 1

Next


CreatePalette(LogPal)


OpenClipboard(Me.Handle)


EmptyClipboard()


SetClipboardData(CF_PALETTE, Hpal)

CloseClipboard()



=====

Public Declare Auto Function CreatePalette Lib "gdi32" (ByRef lpLogPalette
As LOGPALETTE) As IntPtr

Public Declare Auto Function SetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer, ByRef hPal As IntPtr) As IntPtr

Public Declare Auto Function EmptyClipboard Lib "user32.dll" () As Boolean

Public Declare Auto Function OpenClipboard Lib "user32.dll" (ByVal hWnd As
IntPtr) As Boolean

Public Declare Auto Function CloseClipboard Lib "user32.dll" () As Boolean

<StructLayout(LayoutKind.Sequential, Pack:=1, CharSet:=CharSet.Auto)> _

Public Structure LOGPALETTE

Public palVersion As Short

Public palNumEntries As Short

<MarshalAs(UnmanagedType.ByValArray, SizeConst:=1024)> Public palEntry() As
Byte

End Structure
 
M

Michael Phillips, Jr.

Public Declare Auto Function CreatePalette Lib "gdi32" (ByRef lpLogPalette
As LOGPALETTE) As IntPtr

I recommend that you declare the above as follows:
Public Declare Auto Function CreatePalette Lib "gdi32" (ByVal lpLogPalette
As InPtr) As IntPtr

You can avoid the default Marshaling behavior which may or may not succeed
correctly by doing the Marshaling yourself

Marshal your managed LOGPALETTE to an unmanaged ptr to allocated memory that
is laid out as a LOPGPALETTE
Public Declare Auto Function SetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer, ByRef hPal As IntPtr) As IntPtr

The above declaration is not correct!

It should be declared as follows:
Public Declare Auto Function SetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer, ByVal hPal As IntPtr) As IntPtr
 
A

active

You can avoid the default Marshaling behavior which may or may not succeed
correctly by doing the Marshaling yourself

Marshal your managed LOGPALETTE to an unmanaged ptr to allocated memory
that is laid out as a LOPGPALETTE
This is what gave me the most problems when I first started.

I could not get past the fact that a the marshaler doesn't support struct
arrays inside other structs.

I've many examples that I tried and could not get to work, for example:

'Private Structure LOGPALETTE

' Dim palVersion As Short

' Dim palNumEntries As Short

' 'Enough for 256 colors

' <VBFixedArray(255)> Dim palPalEntry() As PALETTEENTRY

I've commented them out and left then as notes to myself.



thanks
 
M

Michael Phillips, Jr.

Marshaling is not intuitive.

You basically need to create an unmanaged chunk of memory and lay it out
exactly as you would if you were coding the "C" structures without the magic
of a compiler.

It is tedious but that is how the default Marshaler works. It Blits a
managed chunk of memory to an unmanaged chunk of memory. Everything is laid
out sequentially in memory.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top