
אם אתם עובדים ב-Windows, במוקדם או במאוחר תרצו לחלץ את הסמל מקובץ הרצה או קובץ .ico כדי לעשות בו שימוש חוזר באפליקציה אחרת, ברשימה או עבור הבדיקות שלכם. במערכת האקולוגית של Windows, ישנן מספר דרכים לעשות זאת: ה-API המקורי (ExtractIconExW, GetDIBits…), Python עם ctypes, .NET עם Icon.ExtractAssociatedIcon, ואפילו פתרונות ישנים יותר ב-Visual Basic הקלאסי . לכל אחד מהם יש את הניואנסים והיתרונות שלו. אתם יכולים לבדוק מדריך ב-NirSoft Utilities.
במדריך זה, אנו מאגדים את כל החלקים הללו במקום אחד. תראו הכל, החל כיצד לבקש את HICONs "קטנים" ו"גדולים" שנחשפים על ידי קובץ .exe או .ico מ-Windows, ועד לקריאת מפת הסיביות בפורמט BGRA, המרתה ל-RGBA במידת הצורך עבור PyGame או Pillow, וכיצד להציג את הסמל המשויך לקובץ ב- Windows Forms ListView באמצעות ImageList . אנו גם סוקרים גישה קלאסית של VB לרישום ואחסון סמלים ודנים בשיקולים מעשיים.
מה יש בתוך קבצי .ico ו-.exe: גדלים והיכן הם מופיעים
סמל יחיד יכול להכיל מספר תמונות פנימיות בגדלים שונים (16x16, 32x32, 48x48 וכו'). ב-Windows, מקובל להשתמש בסמלים "קטנים" (16x16) ב-Windows וב-File Explorer, שם מטמונים כמו Windows thumbs.db יכולים לאחסן תמונות ממוזערות, בעוד שסמלים "גדולים" (32x32) מוצגים בשורת המשימות וכאשר עוברים ביניהם באמצעות Alt+Tab. קבצי הפעלה (.exe) וספריות (.dll) מאחסנים סמלים כמשאבים , וקבצי ה-.ico עצמם הם מכלים לרזולוציות ועומקי צבע מרובים.
ממשק ה-API של Windows הופך את זה לקל: בעזרת ExtractIconExW (בתוך קובץ shell32.dll ) ניתן לבקש את הסמל הקטן או הגדול שקובץ חושף, בין אם זה קובץ .exe או .ico. פונקציה זו מחזירה מטפלי HICON מוכנים לציור או, אם רוצים להעמיק, לקריאת מפת הסיביות הבסיסית ולתפעל את הפיקסלים.
אם אתם זקוקים לנתונים הגולמיים (לדוגמה, בייט של מפת סיביות לעיבוד עם הספריות שלכם), התהליך כולל את GetIconInfo כדי להמיר את ה-HICON ל-HBITMAP שלו ואת GetDIBits כדי לרוקן את התוכן למאגר שאתם שולטים בו. זה נותן לכם שליטה מלאה: גודל מדויק, סדר בייט, שקיפות ופורמט.
חשוב לזכור את הפורמט הפנימי ש-Windows מחזיר במקרים אלה: בדרך כלל אנו מתמודדים עם 32 סיביות לפיקסל ב-BGRA (כחול, ירוק, אדום ואלפא). הבדל זה מ-RGBA קונבנציונלי חשוב אם אתם מתכננים להעביר את התמונה למנועי הדפסה או ספריות שמצפים לסדר ערוצים שונה.
המפתח הוא להבין שה-API מחזיר נתונים בסדר הקריאה של GDI עצמו. עם כותרת וגובה שלילי נכונים, ניתן לקבל את התמונה מלמעלה למטה , מה שמקל על השימוש הישיר בספריות אחרות מבלי שיהיה צורך להפוך את המאגר.

חילוץ אייקונים באמצעות Python ו-Windows API (ctypes): מ-HICON לבייטים של תמונות
אם אתם משתמשים בפייתון על Windows, הדרך הישירה ביותר כיום היא להשתמש ב- ctypes כדי לקרוא ל-API המקורי . ייתכן שנשקול את pywin32, אך יש פרט חשוב: pywin32 אינו חושף GetDIBits , ופונקציה זו נחוצה כדי לקבל את הפיקסלים בפועל מ-HICON . לכן, ctypes היא הדרך המעשית להגדיר מבנים, אבות טיפוס וקריאות בדיוק כפי ש-API של Windows מצפה.
הרצף הטיפוסי הוא כדלקמן: צור בקר תחום תואם (CreateCompatibleDC), קרא ל-ExtractIconExW כדי לקבל את הסמל הקטן או הגדול, פתור את הצבע ומסוך מפות סיביות באמצעות GetIconInfo , הכין BITMAPINFO עם כותרת של 32 סיביות, ציון גובה שלילי עבור קואורדינטות מלמעלה למטה, ולבסוף dump את הפיקסלים באמצעות GetDIBits לתוך מאגר של Python (ctypes.create_string_buffer).
מ-ctypes, ייבוא byref, sizeof מ-ctypes, ייבוא c_int, c_void_p מ-ctypes, ייבוא create_string_buffer מ-ctypes.wintypes, ייבוא BOOL, BYTE, DWORD, HBITMAP, HDC, HGDIOBJ, HICON, LONG, LPCWSTR, LPVOID, UINT, WORD import ctypes # קבועים נפוצים BI_RGB = 0 DIB_RGB_COLORS = 0 # מבנים מינימליים עבור מחלקת GDI ICONINFO(ctypes.Structure): _fields_ = class RGBQUAD(ctypes.Structure): _fields_ = class BITMAPINFOHEADER(ctypes.Structure): _fields_ = class BITMAPINFO(ctypes.Structure): _fields_ = # קבצי DLL shell32 = ctypes.WinDLL('shell32', use_last_error=True) user32 = ctypes.WinDLL('user32', use_last_error=True) gdi32 = ctypes.WinDLL('gdi32', use_last_error=True) # אבות טיפוס חיוניים shell32.ExtractIconExW.argtypes = shell32.ExtractIconExW.restype = UINT user32.GetIconInfo.argtypes = user32.GetIconInfo.restype = BOOL user32.DestroyIcon.argtypes = user32.DestroyIcon.restype = BOOL gdi32.CreateCompatibleDC.argtypes = gdi32.CreateCompatibleDC.restype = HDC gdi32.GetDIBits.argtypes = gdi32.GetDIBits.restype = c_int gdi32.DeleteObject.argtypes = gdi32.DeleteObject.restype = BOOL # ספירה פשוטה עבור מחלקת גדלים IconSize: SMALL = 1 LARGE = 2 @staticmethod def to_wh(size): return {IconSize.SMALL: (16, 16), IconSize.LARGE: (32, 32)} # פונקציה ברמה גבוהה (סקיצה) def extract_icon(filename, size): dc = gdi32.CreateCompatibleDC(0) if dc == 0: raise ctypes.WinError() hicon = HICON() # אנו מזמינים את הגדול או הקטן לפי הצורך out_large = byref(hicon) if size == IconSize.LARGE else None out_small = byref(hicon) if size == IconSize.SMALL else None got = shell32.ExtractIconExW(filename, 0, out_large, out_small, 1) if got != 1: raise ctypes.WinError() info = ICONINFO() if not user32.GetIconInfo(hicon, byref(info)): user32.DestroyIcon(hicon) raise ctypes.WinError() w, h = IconSize.to_wh(size) bmi = BITMAPINFO() ctypes.memset(byref(bmi), 0, sizeof(bmi)) bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biWidth = w bmi.bmiHeader.biHeight = -h # שלילי עבור מלמעלה למטה bmi.bmiHeader.biPlanes = 1 bmi.bmiHeader.biBitCount = 32 bmi.bmiHeader.biCompression = BI_RGB bmi.bmiHeader.biSizeImage = w * h * 4 סיביות = create_string_buffer(bmi.bmiHeader.biSizeImage) מועתק = gdi32.GetDIBits(dc, info.hbmColor, 0, h, סיביות, byref(bmi), DIB_RGB_COLORS) אם מועתק == 0: # ניקוי מינימלי if info.hbmColor: gdi32.DeleteObject(info.hbmColor) if info.hbmMask: gdi32.DeleteObject(info.hbmMask) user32.DestroyIcon(hicon) raise ctypes.WinError() # ניקוי GDI ו-HICON if info.hbmColor: gdi32.DeleteObject(info.hbmColor) if info.hbmMask: gdi32.DeleteObject(info.hbmMask) user32.DestroyIcon(hicon) החזרת ביטים # בתים BGRA
בעזרת האמור לעיל, כעת תוכלו לקבל את הסמל הקטן או הגדול של כל קובץ הרצה. לדוגמה, אם תרצו לנסות זאת עם מפרש הפייתון עצמו (הנתיב זמין ב- sys.executable ), פשוט חלצו את שני הגדלים ועבדו עם המאגרים שלהם.
# דוגמת שימוש import sys small_icon = extract_icon(sys.executable, IconSize.SMALL) large_icon = extract_icon(sys.executable, IconSize.LARGE)
התוצאה היא בלוק של בתים שבו כל פיקסל תופס 4 בתים ב-BGRA . ב-16×16 יהיו 16×16×4 = 1024 בתים, וב-32×32, 4096. הסדר הוא שורה אחר שורה ועמודה אחר עמודה, מלמעלה למטה ומשמאל לימין אם השתמשתם בגובה שלילי בכותרת, מה שמפשט את קריאת האינדקס הנכון עבור כל אחד (x, y).
אם ברצונך לאמת את הגודל, תוכל להדפיס את אורך המאגר 16x16 ולבדוק שהוא 1024 בתים . בדיקה זו חוסכת לך הפתעות בעת עבודה עם משאבים הכוללים רזולוציות ועומקי צבע מרובים.
כדי להמחיש פיקסל אחר פיקסל ולהעריך את סדר הקריאה, טכניקת הוראה שימושית היא להשתמש ב-PyGame: חוצים את המאגר שורה אחר שורה, ממירים כל BGRA ל-RGBA, וצובעים עם set_at. ניתן אפילו להכניס השהייה ראשונית קטנה כדי "לראות" כיצד הסמל מתמלא על המסך.
# עבדו את האייקון בגודל 32x32 עם PyGame, תוך המרת BGRA -> RGBA מ- winicon import IconSize, extract_icon import pygame import sys pygame.init() screen = pygame.display.set_mode((320, 240)) icon_size = IconSize.LARGE w, h = IconSize.to_wh(icon_size) icon_large = extract_icon(sys.executable, icon_size) rendered = False offset_x, offset_y = 50, 50 while True: עבור אירוע ב- pygame.event.get(): if event.type == pygame.QUIT: sys.exit() screen.fill((0, 0, 0)) עבור שורה בטווח(h): עבור עמודה בטווח(w): index = שורה * w * 4 + עמודה * 4 b, g, r, a = icon_large color = (r, g, b, a) screen.set_at((offset_x + col, offset_y + row), color) אם לא מעובד: pygame.time.wait(10) pygame.display.flip() מעובד = True
בפועל, לא תמיד תרצו להתאים פיקסלים ידנית. כדי לשמור את הסמל בדיסק כתמונה, Pillow (PIL) מקל על הדברים הרבה יותר: אתם יוצרים את התמונה מהבייטים באמצעות מצב 'RGBA', תוך ציון שהקלט הוא 'BGRA', ושומרים אותה כ-BMP, PNG, או כל פורמט אחר שמתאים . מתאם כמו win32_icon_to_image(icon_bits, size) מטפל בהמרה הזו עבורכם.
# המרת מאגר BGRA לתמונת PIL ושמירה מייבוא PIL Image def win32_icon_to_image(icon_bits, size): w, h = IconSize.to_wh(size) img = Image.frombytes('RGBA', (w, h), icon_bits, 'raw', 'BGRA') return img img_small = win32_icon_to_image(small_icon, IconSize.SMALL) img_large = win32_icon_to_image(large_icon, IconSize.LARGE) img_small.save('python1.bmp') img_large.save('python2.bmp')

הצגת הסמל המשויך לקובץ ב-.NET (WinForms): ListView + ImageList
צורך נפוץ נוסף הוא להציג, לצד כל שם קובץ, את הסמל ש-Windows מקשר לסיומת שלו (לדוגמה, .docx עם סמל Word). ב-WinForms, ניתן לעשות זאת באמצעות Icon.ExtractAssociatedIcon ואחסון התוצאות ב- ImageList המקושר ל-ListView שתצורתו נקבעה בתצוגת SmallIcon.
הרעיון הוא לעבור על הקבצים בספרייה, ולבדוק אם בכל אחד מהם כבר יש תמונה ב-ImageList עם מפתח התואם את הסיומת ; אם לא, חלץ את הסמל והוסף אותו. זה מונע כפילויות ומאיץ את הטעינה. עבור פריט ListView, הקצה למאפיין ImageKey את הסיומת הזו והוסף את הפריט. אם משהו משתבש, תוכל להשתמש בחלופה כמו SystemIcons.WinLogo.
ListView listView1 = new ListView(); ImageList imageList1 = new ImageList(); listView1.SmallImageList = imageList1; listView1.View = View.SmallIcon; // לולאה בין קבצים (דורש שימוש ב-System.IO) var dir = new System.IO.DirectoryInfo(@"C:\"); foreach (var file in dir.GetFiles()) { var item = new ListViewItem(file.Name); var key = file.Extension; if (!imageList1.Images.ContainsKey(key)) { var icon = System.Drawing.Icon.ExtractAssociatedIcon(file.FullName); imageList1.Images.Add(key, icon); } item.ImageKey = key; listView1.Items.Add(item); }
כדי לקמפל אותו במהירות, הדבק את הקוד לטופס של Windows Forms , צור והגדר את ListView ו-ImageList בבנאי או באירוע Load, וזכור לייבא את מרחב השמות System.IO . אינך צריך לטפל ידנית ב-HICON או בפיקסלים; אתה מסתמך לחלוטין על שיוכי אייקוני מערכת.
גישה קלאסית עם Visual Basic (מציג/חילוץ ושמירה ל-BMP)
לפני .NET וערימות מודרניות, תוכנות רבות ב-VB 4/5/6 כבר ביצעו משימה זו על ידי קריאה ל-API: ExtractIcon מ-shell32 כדי להשיג HICON, DrawIcon כדי לצייר ב-PictureBox, ו- BitBlt כדי להעתיק למשטח אחר וליצור רצועת תמונות ממוזערות. תוכנות אלו ספרו את הסמלים בקובץ .exe או .dll על ידי הגדלת אינדקס הסמלים עד שהפונקציה הפסיקה להחזיר תוצאות.
תהליך העבודה האופייני היה: לאחר בחירת קובץ, התחל באינדקס 0, ובזמן ש- ExtractIcon החזיר מזהה זיהוי תקין, צייר אותו ב-PictureBox, רענן ועבר לאינדקס הבא. במקביל, באמצעות BitBlt , הרשת 32x32 הועתקה למשטח שני במיקומי היסט (לדוגמה, ל-X = 2 + אינדקס × תא) כדי לראות את כל הסמלים המוטמעים במבט חטוף.
כדי לשמור, VB הציעה שתי אפשרויות עם SavePicture : שמירת המאפיין Picture כפי שהוא או המאפיין Image (האחרון תמיד כ-BMP). בנוסף, חלק מהטפסים כללו בורר קבצים עם מסננים לסיומות קבצים כגון .ico, .bmp, .exe, .dll ואחרים, ואף הציגו תצוגה מקדימה אם הקובץ היה תמונה.
גישה זו נותרה תקפה להבנת אופן האינטראקציה עם ה-API משפות Windows "מסורתיות". עם זאת, אם אתם זקוקים למאגר הפיקסלים כיום , עדיף להשתמש ב-GDI וב-GetDIBits (או, ב-.NET מודרני, בשיטות שמסדרות את התמונה) במקום להסתמך אך ורק על DrawIcon עבור פקדים.
פרט חשוב: גודל, סדר בתים וקריאת שורות/עמודות
בעת אחזור פיקסלים באמצעות GetDIBits, ציון biHeight כשלילי מגדיר את מקור הקואורדינטות לפינה השמאלית העליונה , וזה מה שספריות גרפיקה רבות מצפות לו. אם לא תעשו זאת, תקבלו מאגר מלמטה למעלה ותצטרכו להפוך את הבלוקים שורה אחר שורה.
בפורמט 16x16, השורה הראשונה תופסת 16x4 = 64 בייטים (4 לפיקסל). לכן, ההיסט לשורה n הוא nx64 וההיסט לעמודה m הוא mx4. בפורמט 32x32, כל שורה היא 128 בייטים , סה"כ 4096. סדר ה-BGRA (B, G, R, A) הוא מה שקריאות GDI אלו מחזירות; אם ספריית היעד שלך רוצה RGBA, תצטרך לסדר מחדש את הערוצים כפי שנעשה עם PyGame או לציין זאת בעת יצירת התמונה עם Pillow.
אם אתם מתכוונים לתפעל שקיפות, ערוץ האלפא יהיה הבייט הרביעי של הפיקסל. זכרו שגם סמלים משתמשים במסכה (hbmMask) ; כשעובדים עם BGRA של 32 סיביות, שקיפות בדרך כלל כבר מיוצגת באלפא של hbmColor , כך שניתן להתעלם מהמסכה בשימושים נפוצים.
סמלים במשאבים ובהחלפה: שיקולים מהירים
קבצי הרצה וספריות מאחסנים את הסמלים שלהם כמשאבים . ניתן לחלץ אותם באמצעות ממשק ה-API כפי שמוסבר, או, אם אתם מעדיפים גישה ויזואלית, ישנם עורכי משאבים וכלי עזר עם ממשק גרפי המאפשרים לכם להחליף או לייצא סמלים. אם אתם מתכננים לבצע אוטומציה, הגישה התכנותית באמצעות HICON, GetIconInfo ו-GetDIBits מעניקה לכם שליטה מלאה ושחזור.
כדי לקרוא סמלים "הקשורים" להרחבות (מבלי לחלץ אותם מקובץ ה-.exe עצמו), .NET מפשט את התהליך בעזרת Icon.ExtractAssociatedIcon . שיטה זו מסתמכת על שיוכי רישום ומחזירה את הסמל שהמערכת תציג עבור כל קובץ, וזה אידיאלי עבור רשימות, חוקרים וצופים.
שיטות עבודה מומלצות וניקוי משאבים
עבודה עם GDI כרוכה בניהול מפורש של מטפלים וזיכרון . בכל פעם שאתה מקבל HICON או HBITMAP, עליך לתכנן את מחזור החיים שלו: להשמיד את ה-HICON באמצעות DestroyIcon ולשחרר את מפות הסיביות באמצעות DeleteObject . שכחה לעשות זאת עלולה להוביל לדליפות משאבים, אשר, ביישומים עם קריאות רבות, גורמות לדליפות משאבים ולהתנהגות לא יציבה.
ב-Python/ctypes, נוח לאגור את הניקוי במנהל פונקציות או הקשר קטן . ודאו תמיד שאתם מפעילים ניקוי על ענפי שגיאה, במיוחד אם GetDIBits מחזירה 0 או אם ExtractIconExW לא מחזירה את מספר האייקונים הצפוי. כמו כן, יש לאמת שה-DC המוחזר על ידי CreateCompatibleDC תקין לפני שתמשיכו.
מתי לבחור כל גישה
אם אתם צריכים רק להציג את הסמל "ש-Windows יציג" עבור קובץ, .NET עם ExtractAssociatedIcon הוא פשוט ויעיל. אם אתם רוצים לקרוא את מפת הסיביות האמיתית של סמל המוטמע בקובץ .exe או .ico כדי לערוך, להמיר או לשמור אותו בעצמכם, ה- API עם ctypes נותן לכם את כל השליטה הדרושה. עבור כלים מדור קודם או בנייה מהירה של אב טיפוס, גישת VB הקלאסית נותרת להמחשה ופונקציונלית בהקשר שלה.
אם המטרה שלכם היא לשמור אייקונים כתמונות בשכבות אלפא ללא אובדן נתונים, שקלו פורמטים כמו PNG באמצעות Pillow, תוך הקפדה על סדר ערוצים נכון. BMP הוא פשוט ותואם באופן נרחב, אך אם אתם מעדיפים דחיסה ושקיפות, PNG או ICO (אם ארוזים מחדש) עשויים להתאים יותר.
על ידי שילוב ממשקי API מקוריים, Python ו-.NET, תוכלו לטפל בכל דבר, החל מהצגת סמלים הקשורים להרחבה ועד לחילוץ בייט BGRA עם שליטה מלאה על גודל, שקיפות וסדר קריאה, כמו גם רישום סמלים המוטמעים בקבצי הרצה מדור קודם או מודרניים. מגוון גישות זה מאפשר לכם ליצור הכל, החל ממציגים ורשימות ועד לייצוא סמלים וצינורות עריכה.
כותב נלהב על עולם הבתים והטכנולוגיה בכלל. אני אוהב לחלוק את הידע שלי באמצעות כתיבה, וזה מה שאעשה בבלוג הזה, אראה לכם את כל הדברים הכי מעניינים על גאדג'טים, תוכנה, חומרה, טרנדים טכנולוגיים ועוד. המטרה שלי היא לעזור לך לנווט בעולם הדיגיטלי בצורה פשוטה ומשעשעת.