• 0
ahmed_fa

كيف يمكن جلب طول وعرض صورة (JPEG) من الهيدر الخاص بها.

سؤال

السلام عليكم

 

سؤالى للاسف ما أعرفش هيتم الرد علية بسرعة ولا شكلى هنتظر كثيير, على العموم هتوكل على الله .

 

أنا عاوز اجيب طول وعرض الصورة من الهيدر(Header) الخاص بها .

طبعا أنا قرأت كثيير عن بنية الهيدر الخاص بملف الـ JPEG بس للاسف بردة ما فهمتش كوايس. 

 

ودة الكود اللى أنا عملتة بس النتيجة للاسف بيظهر أرقام لا تمثل الارقام الحقيقة للعرض والطول للصورة .

unsigned short width, height;FILE *file;file = fopen("C:\\loser.jpg", "rb");if(file != NULL){   fseek(file, 2, SEEK_SET);	   fread(&width, sizeof(unsigned short), 1,file);   fread(&height, sizeof(unsigned short), 1,file);   printf("Width: %d, Height: %d\n", width, height);    fclose(file);}else{   printf("File does not exists.");}

والمطلوب:

  • أنا كنت عاوز حد يشرح لى بنية الهيدر لهذا الملف.
  • وكيف يمكن جلب طول وعرض الصورة من الهيدر الخاص بالصورة, مع إضافة مثال بلغة C يوضح كيف يمكن جلب البيانات من الملف .

 

شكل الملف فى محرر الهكس (Hex editor)

 

1821939406edeede48611e6b3b61675f9bcd4aac

 

 

بعض الوصلات التى تشرح بنية هذا الملف (JPEG).

تم تعديل بواسطه مستر برمجة
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

9 إجابة على هذا السؤال .

  • 0

أشكرك أخى على ردك, وعلى الوصلة المرفقة.

 

لكن أنا قرآت مواضيع كثيير تتكلم عن هيدر الملف (JPEG), وقرآت أسئلة وأجوبة لكن للاسف لم أفهم جيدا الفكرة .

ولهذا السبب أنا عاوز أحد من الاخوة فى المنتدى يكون عندة فكرة بالموضوع دة عشان يفهمة لى, أنا عاوز اعرف الفكرة من الاساس, ويعطينى مثال مع الشرح.

 

يعنى عاوز الجواب يكون صناعة عربية بأيدى عربية :yes: 

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

المصادر التي وضعتها يظهر أنها ناقصة، توجد تفاصيل أكثر عن بنية jpeg في ويكيبيديا : http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure وهذه الصفحة أفادتني: http://www.daevius.com/information-jpeg-file-format.

 

* صورة الـjpeg تبدأ باباتين 0xff 0xd8 يمكنك استخدامها للتأكّد من أنها صورة jpeg.

* ثم تليها عدّة بنى كل بنية تبدأ ببايتين لمعرّف البنية (marker) أحدهما 0xFF والآخر يختلف حسب البنية ويليها بايتين يمثلاًن حجم البنية بدون البايتين الخاصة بالمعرّف (لكن البايتين الخاصة بالحجم داخله في الحسبة)، كالتالي:

 

---------------------------------------------------------------------------------------------------

معرّف الصورة (بايتين) 0xff 0xd8

---------------------------------------------------------------------------------------------------

معرّف البنية 1 (بايتين أولهما 0xff)

حجم بيانات البنية (بايتين، وهذاين البايتين داخلان في الحسبة)

باينات البنية

---------------------------------------------------------------------------------------------------

معرّف البنية 2 (بايتين أولهما 0xff)

حجم بيانات البنية (بايتين، وهذاين البايتين داخلان في الحسبة)

باينات البنية

---------------------------------------------------------------------------------------------------

معرّف البنية 3 (بايتين أولهما 0xff)

حجم بيانات البنية (بايتين، وهذاين البايتين داخلان في الحسبة)

باينات البنية

---------------------------------------------------------------------------------------------------

معرّف البنية 4 (بايتين أولهما 0xff)

حجم بيانات البنية (بايتين، وهذاين البايتين داخلان في الحسبة)

باينات البنية

---------------------------------------------------------------------------------------------------

....

 

وهكذا، خذ مثلاً هذه الصورة:

 

post-231926-0-89834600-1376767262.jpg

 

بصمة الـmd5 لها: 292681e45105ffd284e2fde8c1e295fe

 

تفاصيلها:

 

post-231926-0-04572200-1376767256.png

 

أولاً ترتيب البايتات في jpeg يخزّن كـbig-endian أي بالترتيب الصحيح على غير العادة، في أغلب الملفات تخزّن البيانات كـlittle-endian بالمقلوب، لو أردت قرأءة قيمة من الصورة يلزمك قلبها أولاً (انظر هنا). لاتنسى أن تجعل محرر الـhex يستخدم big-endian في عرض الأرقام، اضغط على أيقونة موترولا.

 

في الصورة:

 

* المربع الأخضر، وهو معرّف الصورة 0xff 0xd8.

* المربع الأحمر معرّف بنية.

* المربع الأزرق حجم بنية.

* الخطوط بيانات البنية.

 

قد تختلف البنى بين صورة وأخرى في الحجم وفي الترتيب، وقد تجد بنية في صورة وفي صورة أخرى لاتجدها، لا أحظ أن هناك هناك خطأ في الصورة حيث تكررت البنية 0xff 0xdb مرتيين، لذا يستحيل أن تعتمد على موضوع البنية.

 

البنية التي تحمل أبعاد الصورة تسمى SOF0، معرفها 0xff 0xc0 وتركيبها:

 

* معرّف البنية (بايتين 0xff 0xc0).

* حجم البنية (بايتين، كما قلت أن بايتات الحجم محسوبة).

* بايت واحد (لا أعرف غرضه).

* الإرتفاع (بايتين)، المربع الأسود.

*العرض (باتين)، المربع الأسود.

 

وأيضاً تفاصيل أخرى لاتهمنا.

 

لاستخراج الحجم يلزم القيام بالتالي:

 

1. أولاً اقرأ أول باتين من الصورة وتأكّد من أنهما 0xff 0xd8.

2. ثم ابدأ بقرأءة رأس البنية (البايتين الخاصة بالمعرف والبايتين الخاصة بالحجم):

** اذا كان أوّل بايت في المعرّف ليس 0xff، فالاغلب أن هناك خطأ ما.

** اذا كان أول بايت 0xff والبايت الثاني 0xc0، فأنت وجدت البنية SOF0.

** غير هذا انتقل للبنية التالية عن طريق قلب بايتات الحجم وطرح 2 منها (كما قلت أن الحجم داخل).

 

التطبيق:

#include <stdio.h>#include <assert.h>// unsigned char  بايت واحد// unsigned short بايتين// لقلب البايتات#define little_endian(x) ((x >> 8 | ((x << 8) & 0xff00)) & 0xffff)#define OSI     0xd8ff // معرّف الصورة بالمقلوب#define SOF0    0xc0   // معرّف البنية التي تحمل معلومات الصورة// سيرصّ المصرف البنية على أربع بايتات افتراضياً مما سيخلق فراغات فيها لذا// قمّ برصّ البنية على بايت واحد كي لاتكون هناك فراغات وتحصل أخطأ#pragma pack(1)// معلومات البنيةtypedef struct __JPEGMARKER {    unsigned char  MarkerName[2]; // اسم البنية    unsigned short SegmentLength; // حجمها} JPEGMARKER;// معلومات البنية التي تحمل معلومات العرض والارتفاعtypedef struct __JPEGSOF0 {    unsigned char  Precision;       // لا أعلم غرضه    unsigned short Height;          // الإرتفاع، البايتات مقلوبة    unsigned short Width;           // العرض، البياتات مقلوبة} JPEGSOF0;// اعد الرص للوضع الافتراضي#pragma pack()int main(int argc, char **argv) {    FILE *file;    JPEGSOF0 sof0;    JPEGMARKER marker;    unsigned short osi;    file = fopen("images.jpg", "rb");    if( file == NULL ) {        perror("Error");        return -1;    }    // نريد التأكّد من أنها صورة jpeg    fread(&osi, sizeof(unsigned short), 1, file);    if( osi != OSI ) {        printf("Error: This is probably not a JPEG image\n");        fclose(file);        return -1;    }    // طالما أننا لم نصل لنهاية الملف    do {        // اقرأ البنية        fread(&marker, sizeof(JPEGMARKER), 1, file);        // اذا لم يبدأ المعرف بـ"اف-اف" فيظهر أن خطأ حصل        if( marker.MarkerName[0] != 0xff ) {            printf("Error: invalid marker\n");            fclose(file);            return -1;        }        // اذا وجدنا البنية التي تحمل بيانات الصورة نتوقف        if( (marker.MarkerName[0] == 0xff) &&            (marker.MarkerName[1] == SOF0) ) {            break;        }        // ننتقل للبنية التالية، البنية التالية تساوي حجم البنية - 2، هذا لأن البايتين الخاصين بالحجم محسوبين في حجم البنية        // لا تنسى أن البيانات مخزّنة بالمقلوب، يلزمنا عكسها        fseek(file, little_endian(marker.SegmentLength) - 2, SEEK_CUR);    } while( feof(file) == 0 );    // لنتأكّد من أننا عثرنا على البنية التي تحوي معلومات الصورة    assert( (marker.MarkerName[0] == 0xff) && (marker.MarkerName[1] == SOF0) );    // لنقرأ البنية    fread(&sof0, sizeof(JPEGSOF0), 1, file);    // اسم البنية    printf("Marker.name  => 0x%.2X 0x%.2X\n", marker.MarkerName[0],                                              marker.MarkerName[1]);    // حجمها    printf("Marker.size  => %hu byte\n", little_endian(marker.SegmentLength));    // ارتفاع الصورة    printf("Image.height => %hu pixel\n", little_endian(sof0.Height));    // عرض الصورة    printf("Image.width  => %hu pixel\n", little_endian(sof0.Width ));    fclose(file);    return 0;}

post-231926-0-04572200-1376767256_thumb.

post-231926-0-89834600-1376767262.jpg

تم تعديل بواسطه Mr.B
6

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

بجد مش عارف أقول لك أية أخى Mr.B .

 

الشرح فعلا جميل وشامل الفكرة كاملة, بس فعلا الموضوع طلع معقد بعض الشئ وما كنتش فاكر أنة بهذا الشكل .

مع العلم أن أننا لازلت لم أفهم الموضوع كاملا لكن أنا محتاج أنى أعيد قراءة ردك مرة أخرى عشان المعلومات تثبت فى المخ أكثر.

 

وعلى فكرة أنا اللى فرحنى فى كل هذة القصة أن الرد كان بأيدى عربية, لانى بحثت كثييرعلى النت عشان أشوف أى شئ باللغة العربية يتكلم عن هذا الموضوع لكن للاسف لاحياة لمن تنادى (للاسف المحتوى العربى سئ للغاية وعشان كدة دة حالنا الان).

 

أما المحتوى الاجنبى جيد وكل شئ لكن المشكلة أن فية مصطلحات وأشياء لم أفهمها مثل (big-endain, SOF0, وغيرعا) لذلك لم تكتمل الفكرة عندى من المواضيع الاجنبية التى تتكلم عن هذا الموضوع .

 

بعض الاستفسارات أرجو الرد عليهم:

  • أرجو شرح بسيط عن هذا المايكرو
#define little_endian(x) ((x >> 8 | ((x << 8) & 0xff00)) & 0xffff)
  • هل عندما اقراء الصورة بتكون بالشكل big_endian وعشان كدة بنستخدم المايكرو little_endian عشان البايتات تتعكس وتكون بطريقة little endian.

 

 

جزاك الله كل خير --- (الرد بتاعك فعلا بذلت فية مجهود).

تم تعديل بواسطه مستر برمجة
1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

SOF0 مجرد اسم وليس مصطلح تقني.

 

big-endian و little-endian يعني ترتيب البايتات، في هذا المثال:

#include <stdio.h>int main(int argc, char **argv) {    FILE *file;    unsigned short data = 0x1122;    if( ( file = fopen("dump.txt", "wb") ) == NULL ) {        perror("Error");        return -1;    }    fwrite(&data, sizeof(data), 1, file);    fclose(file);    return 0;}

قيمة المتغير data تساوي 0x1122 لكن لو فتحت الملف dump.txt بمحرر الـhex ستجد أنه مكتوب 0x22 0x11 أي أن البايتات مقلوبة، هذا لأن معالجات أنتل little-endian، لو كان المعالج big-endian ستجدها 0x11 0x22، الموضوع أبسط مما تتصور:

 

0x1122 أصبحت 0x22 0x11 يعني مقلوب، اذا فهو little-endian.

0x1122 أصبحت 0x11 0x22 يعني ترتيب البيانات الأصلي، اذا فهو big-endian.

 

أغلب صيغ الملفات تستخدم little-endian، لذا عند قراءتها من كمبيوتر يستخدم معالج little-endian لن توجد مشاكل، الا أن صور jpeg تستخدم big-endian، لذا يلزم قلب البيانات كي تتوافق مع المعالج.

 

 

بالنسبة للماكرو، فسبق أن كتبت مقال يشرح التلاعب بالبايتات وتحريكها هنا: التعامل مع البتات والبايتات والعمليات المنطقية عليها

 

مختصر مايقوم له، أنه لو كان الحجم 0x2211، نحن نريد قلبه لـ0x1122، تلك العملية ستنفذ كالتالي:

 

نزيح الحجم بمقدار بايت (8 بتات) لليمين، جعلنا 22 في اليمين
0x2211 >> 8 = 0x0022

نزيح الحجم لليسار بمقدار بايت (8 بتتات) صار 0x221100، الآن نقطعها بعملية AND & مع 0xff00، وتصبح 0x1100:
0x2211 << 8 = 0x221100
0x221100 & 0xff00 = 0x1100

نوصل الشق الأيمن بالايسر عن طريق OR:

0x1100 | 0x0022 = 0x1122

 

اقرأ المقال وستتضح لك، فهي سهلة وستحتاجها كثيراً طالما أنك تبرمج.

 

بالنسبة للـmd5 تجاهلها، كنت أظن أن المنتدى سيضعط الصورة ولم أرغب أن يحدث تغيير عليها دون اكتشافه.

 

الـ#pragma pack(1) اذا لم تفهمها، افتراضياً المصرف يميل الى جعل حجم البنية من مضاعفات الـ4 (أربع بايتات =32 بت = حجم مسجلات المعالج) لأنها تتطلب مجهود أقل من المعالج، انظر لهذا البرنامج:

#include <stdio.h>typedef struct __MyStruct {    int a;    char b[3];} MyStruct;int main(int argc, char **argv) {    FILE *file;    MyStruct myStruct;        myStruct.a = 0x11111111;    myStruct.b[0] = 0x11;    myStruct.b[1] = 0x11;    myStruct.b[2] = 0x11;    printf("%d\n", sizeof(myStruct)); // 8    if( ( file = fopen("dump.txt", "w") ) == NULL ) {        perror("Error");        return -1;    }    fwrite(&myStruct, sizeof(myStruct), 1, file);    fclose(file);    return 0;}

لدينا بنية اسمها MyStruct فيها متغير a نوعه int أي حجمه 4 بايتات، ومتغير char حجمه 3 بايتات، أي يفترض أن حجم البنية 7 بايت، الا أنها ستطبع 8 بايتات (هناك بايت زائد أضافه المصرف كي يصبح الحجم من مضاعفات الأربعة، ولو نظرت للملف dump.txt ستجد أن هناك بايت زائد:

11 11 11 1111 11 11 00         ^      أضافه المصرف

هذا سيسبب مشاكل لأن الرص يختلف باختلاف حجم مسجلات المعالج، معالجات 64بت ترصّ على 8 بايتات - مشكلة-، أيضاً البايتات الزائدة تعني زيادة في حجم الملف. الحل هو توحيد الرصّ وجعله على بايت واحد، عن طريق اضافة:

#pragma pack(1) // جعل الرص على بايت// البنى التي تريد تغيير رصها تضعها في الوسط#pragma pack()  // نهاية الاعداد

نعدل البنية فقط:

#pragma pack(1)typedef struct __MyStruct {    int a;    char b[3];} MyStruct;#pragma pack()

ستجد أن حجمها 7 بايت، تماماً كما هي، وأن البايت الزائد اختفى من الملف:

11 11 11 1111 11 11

هذه المعلومات في هذا الرد ستهمك كثيراً خصوصاً عند التعامل مع الملفات الثنائية ومع بروتوكولات الشبكة وغيرها، حاول تذكرها.

تم تعديل بواسطه Mr.B
4

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

إرادة وإصرار عجيب Mr.B !

 

أغلب من هم في مستواك غير مستعد لكتابة ردود مفصلة و معلومات دقيقة كما تفعل أنت، لذلك أنت مميز ومُلفت.

 

تحياتي لك.

2

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

ما شاء الله أخى Mr.B على هذا المجهود الرائع.

 

 

أنا أسف طولت عليك

فية أخر أستفسارات إن شاء الله

 

هذة الجزئية لم أفهمها جيدا (موضوع طرح 2 من البايت)

  • ** غير هذا انتقل للبنية التالية عن طريق قلب بايتات الحجم وطرح 2 منها (كما قلت أن الحجم داخل).
// ننتقل للبنية التالية، البنية التالية تساوي حجم البنية - 2، هذا لأن البايتين الخاصين بالحجم محسوبين في حجم البنيةfseek(file, little_endian(marker.SegmentLength) - 2, SEEK_CUR);
  • بالنسبة إلى موضوع الإزاحة والتعامل مع البيتات

فى هذة الدالة

#define little_endian(x) ((x >> 8 | ((x << 8) & 0xff00)) & 0xffff)

أستخدامت الرقم 0xff00 و الرقم 0xffff, على أى أساس أستخدمت هذة الارقام تحديدا .

أيضا أستخدمت فى الموضوع اللى أرفقت لى الوصلة بتاعتة (التعامل مع البتات والبايتات والعمليات المنطقية عليها) .

unsigned short number = 0x1122;number &= 0xff 

فى المثال السابق أستخدمت العدد 0xff مع معامل and, لماذا هذا العدد تحديدا .

أنا عاوز أعرف كيف أختار الاعداد التى تناسب عملية محددة لكى أستخدمها مع المعاملات (and, or, xor, not)

تم تعديل بواسطه مستر برمجة
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

حياكم الله ياشباب وشاكر لكم كلامكم الطيب :wub:

 

بالنسبة لسؤالك أخي ، انظر للصورة هنا:

 

post-231926-0-04572200-1376767256.png

 

المربعات الزرقاء تمثل حجم المربع الأزرق (أي بايتين أو حرفين) + عدد الأحرف بعد المربع الأزرق (بيانات البنية) والمربع الأحمر التالي (بداية البنية التالية). مثلاً أول مربع قيمته 0x0010 = 16، أي أن عدد الأحرف بين بين البنية الأولى والثانية 14 حرف، لذا سنستخدم fseek للذهاب للبنية التالية.

 

بالنسبة للسؤال الثانية فمعروف أن AND الواحد مع أي رقم لن تؤثر فيه (1 & 1 = 1 و 1 & 0 = 0)، لكن AND مع الصفر ستعطي دوماً صفر (0 & 1 = 0 و 0 & 0 = 0)، يمكن استغلال AND لأغراض "القطع" حيث تجعل الجزء الذي تريد الإبقاء عليه مقابل للواحد والجزء الذي تريد التخلص منه مقابل للصفر. مثلاً هنا 0x1122 (تقابل في الثنائي 00010001 00100010) أريد الإبقاء على الـ0x22 لذا أقابل الـ22 بواحدات واقابل الـ11 بأصفار:

 

00010001 00100010

11111111 00000000 &

----------------------------

00010001 00000000 = 0x22

 

هذا الرقم 11111111 00000000 الذي استخدمته للقطع يقابل 0x00ff أو فقط 0xff. لو أردت الإبقاء على 11 سأعكس الترتيب:

 

00010001 00100010

00000000 11111111 &

----------------------------

00000000 00100010 = 0x11

 

الرقم 00000000 11111111 يقابل 0xff00.

تم تعديل بواسطه Mr.B
1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

جزاك الله كل خير أخى العزيز


 


أنت عملت اللى عليك وزيادة


تم تعديل بواسطه مستر برمجة
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



سجل دخولك الان

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .